bug 363109 - body of HTTP 304 response is treated as a HTTP/0.9 response to subsequent HTTP request. r=biesi, sr=bz, a=blocker
authorMichal Novotny <michal.novotny@gmail.com>
Wed, 01 Dec 2010 02:02:52 +0200
changeset 58521 37aefdd76a22e0f8ff5549cdd880efd497905e20
parent 58520 3f004b291c654e274f7259173306ab4d94b40cad
child 58522 f1a5bea1d022eedf7cd4c4a4edd7ebc3cf1d29d2
push id17333
push userjst@mozilla.com
push dateFri, 03 Dec 2010 01:00:52 +0000
treeherdermozilla-central@f1a5bea1d022 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbiesi, bz, blocker
bugs363109
milestone2.0b8pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 363109 - body of HTTP 304 response is treated as a HTTP/0.9 response to subsequent HTTP request. r=biesi, sr=bz, a=blocker
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnectionInfo.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -392,16 +392,26 @@ nsHttpConnection::OnHeadersAvailable(nsA
     // transaction completed successfully.
     const char *val = responseHead->PeekHeader(nsHttp::Connection);
     if (!val)
         val = responseHead->PeekHeader(nsHttp::Proxy_Connection);
 
     // reset to default (the server may have changed since we last checked)
     mSupportsPipelining = PR_FALSE;
 
+    // Ignore response to CONNECT from SSL proxy, we need
+    // version of the target server.
+    if (!mSSLProxyConnectStream) {
+        if ((responseHead->Version() > NS_HTTP_VERSION_0_9) &&
+            (requestHead->Version() > NS_HTTP_VERSION_0_9))
+        {
+            mConnInfo->DisallowHttp09();
+        }
+    }
+
     if ((responseHead->Version() < NS_HTTP_VERSION_1_1) ||
         (requestHead->Version() < NS_HTTP_VERSION_1_1)) {
         // HTTP/1.0 connections are by default NOT persistent
         if (val && !PL_strcasecmp(val, "keep-alive"))
             mKeepAlive = PR_TRUE;
         else
             mKeepAlive = PR_FALSE;
     }
--- a/netwerk/protocol/http/nsHttpConnectionInfo.h
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.h
@@ -54,17 +54,18 @@
 class nsHttpConnectionInfo
 {
 public:
     nsHttpConnectionInfo(const nsACString &host, PRInt32 port,
                          nsProxyInfo* proxyInfo,
                          PRBool usingSSL=PR_FALSE)
         : mRef(0)
         , mProxyInfo(proxyInfo)
-        , mUsingSSL(usingSSL) 
+        , mUsingSSL(usingSSL)
+        , mAllowHttp09(PR_TRUE)
     {
         LOG(("Creating nsHttpConnectionInfo @%x\n", this));
 
         mUsingHttpProxy = (proxyInfo && !nsCRT::strcmp(proxyInfo->Type(), "http"));
 
         SetOriginServer(host, port);
     }
     
@@ -117,22 +118,25 @@ public:
     }
 
     const char   *Host() const           { return mHost.get(); }
     PRInt32       Port() const           { return mPort; }
     nsProxyInfo  *ProxyInfo()            { return mProxyInfo; }
     PRBool        UsingHttpProxy() const { return mUsingHttpProxy; }
     PRBool        UsingSSL() const       { return mUsingSSL; }
     PRInt32       DefaultPort() const    { return mUsingSSL ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT; }
+    void          DisallowHttp09()       { mAllowHttp09 = PR_FALSE; }
+    PRBool        IsHttp09Allowed()      { return mAllowHttp09; }
     void          SetAnonymous(PRBool anon)         
                                          { mHashKey.SetCharAt(anon ? 'A' : '.', 2); }
             
 private:
     nsrefcnt               mRef;
     nsCString              mHashKey;
     nsCString              mHost;
     PRInt32                mPort;
     nsCOMPtr<nsProxyInfo>  mProxyInfo;
     PRPackedBool           mUsingHttpProxy;
     PRPackedBool           mUsingSSL;
+    PRPackedBool           mAllowHttp09;
 };
 
 #endif // nsHttpConnectionInfo_h__
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -80,35 +80,16 @@ static NS_DEFINE_CID(kMultiplexInputStre
 
 // mLineBuf is limited to this number of bytes.
 #define MAX_LINEBUF_LENGTH (1024 * 10)
 
 //-----------------------------------------------------------------------------
 // helpers
 //-----------------------------------------------------------------------------
 
-static char *
-LocateHttpStart(char *buf, PRUint32 len)
-{
-    // if we have received less than 4 bytes of data, then we'll have to
-    // just accept a partial match, which may not be correct.
-    if (len < 4)
-        return (PL_strncasecmp(buf, "HTTP", len) == 0) ? buf : 0;
-
-    // PL_strncasestr would be perfect for this, but unfortunately bug 96571
-    // prevents its use here.
-    while (len >= 4) {
-        if (PL_strncasecmp(buf, "HTTP", 4) == 0)
-            return buf;
-        buf++;
-        len--;
-    }
-    return 0;
-}
-
 #if defined(PR_LOGGING)
 static void
 LogHeaders(const char *lines)
 {
     nsCAutoString buf;
     char *p;
     while ((p = PL_strstr(lines, "\r\n")) != nsnull) {
         buf.Assign(lines, p - lines);
@@ -147,16 +128,17 @@ nsHttpTransaction::nsHttpTransaction()
     , mResponseIsComplete(PR_FALSE)
     , mDidContentStart(PR_FALSE)
     , mNoContent(PR_FALSE)
     , mSentData(PR_FALSE)
     , mReceivedData(PR_FALSE)
     , mStatusEventPending(PR_FALSE)
     , mHasRequestBody(PR_FALSE)
     , mSSLConnectFailed(PR_FALSE)
+    , mHttpResponseMatched(PR_FALSE)
 {
     LOG(("Creating nsHttpTransaction @%x\n", this));
 }
 
 nsHttpTransaction::~nsHttpTransaction()
 {
     LOG(("Destroying nsHttpTransaction @%x\n", this));
 
@@ -687,16 +669,70 @@ nsHttpTransaction::Restart()
     // disable pipelining for the next attempt in case pipelining caused the
     // reset.  this is being overly cautious since we don't know if pipelining
     // was the problem here.
     mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
 
     return gHttpHandler->InitiateTransaction(this, mPriority);
 }
 
+char *
+nsHttpTransaction::LocateHttpStart(char *buf, PRUint32 len,
+                                   PRBool aAllowPartialMatch)
+{
+    NS_ASSERTION(!aAllowPartialMatch || mLineBuf.IsEmpty(), "ouch");
+
+    static const char HTTPHeader[] = "HTTP/1.";
+    static const PRInt32 HTTPHeaderLen = sizeof(HTTPHeader) - 1;
+
+    // mLineBuf can contain partial match from previous search
+    if (!mLineBuf.IsEmpty()) {
+        NS_ASSERTION(mLineBuf.Length() < HTTPHeaderLen, "ouch");
+        PRInt32 checkChars = PR_MIN(len, HTTPHeaderLen - mLineBuf.Length());
+        if (PL_strncasecmp(buf, HTTPHeader + mLineBuf.Length(),
+                           checkChars) == 0) {
+            mLineBuf.Append(buf, checkChars);
+            if (mLineBuf.Length() == HTTPHeaderLen) {
+                // We've found whole HTTPHeader sequence. Return pointer at the
+                // end of matched sequence since it is stored in mLineBuf.
+                return (buf + checkChars);
+            }
+            else {
+                // Response matches pattern but is still incomplete.
+                return 0;
+            }
+        }
+        // Previous partial match together with new data doesn't match the
+        // pattern. Start the search again.
+        mLineBuf.Truncate();
+    }
+
+    while (len > 0) {
+        if (PL_strncasecmp(buf, HTTPHeader, PR_MIN(len, HTTPHeaderLen)) == 0) {
+            if (len < HTTPHeaderLen) {
+                // partial HTTPHeader sequence found
+                if (aAllowPartialMatch) {
+                    return buf;
+                } else {
+                    // save partial match to mLineBuf
+                    mLineBuf.Assign(buf, len);
+                    return 0;
+                }
+            }
+
+            // whole HTTPHeader sequence found
+            return buf;
+        }
+        buf++;
+        len--;
+    }
+    return 0;
+}
+
+
 void
 nsHttpTransaction::ParseLine(char *line)
 {
     LOG(("nsHttpTransaction::ParseLine [%s]\n", line));
 
     if (!mHaveStatusLine) {
         mResponseHead->ParseStatusLine(line);
         mHaveStatusLine = PR_TRUE;
@@ -734,16 +770,17 @@ nsHttpTransaction::ParseLineSegment(char
     
     // a line buf with only a new line char signifies the end of headers.
     if (mLineBuf.First() == '\n') {
         mLineBuf.Truncate();
         // discard this response if it is a 100 continue or other 1xx status.
         if (mResponseHead->Status() / 100 == 1) {
             LOG(("ignoring 1xx response\n"));
             mHaveStatusLine = PR_FALSE;
+            mHttpResponseMatched = PR_FALSE;
             mResponseHead->Reset();
             return NS_OK;
         }
         mHaveAllHeaders = PR_TRUE;
     }
     return NS_OK;
 }
 
@@ -772,35 +809,58 @@ nsHttpTransaction::ParseHead(char *buf,
         if (mActivityDistributor)
             mActivityDistributor->ObserveActivity(
                 mChannel,
                 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
                 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START,
                 PR_Now(), LL_ZERO, EmptyCString());
     }
 
-    // if we don't have a status line and the line buf is empty, then
-    // this must be the first time we've been called.
-    if (!mHaveStatusLine && mLineBuf.IsEmpty()) {
-        // tolerate some junk before the status line
-        char *p = LocateHttpStart(buf, PR_MIN(count, 8));
-        if (!p) {
-            // Treat any 0.9 style response of a put as a failure.
-            if (mRequestHead->Method() == nsHttp::Put)
-                return NS_ERROR_ABORT;
+    if (!mHttpResponseMatched) {
+        // If HTTP 0.9 response is allowed (i.e. neither 1.0 nor 1.1 was yet
+        // received on the connection) then do a simple junk detection.
+        // Otherwise find a HTTP response.
+
+        // Value returned by IsHttp09Allowed() can change between calls to this
+        // method, but it can change only from PR_TRUE to PR_FALSE and this is
+        // OK since we can enter this statement multiple time only when the
+        // value id PR_FALSE.
+        nsRefPtr<nsHttpConnectionInfo> ci;
+        mConnection->GetConnectionInfo(getter_AddRefs(ci));
+
+        if (ci->IsHttp09Allowed()) {
+            // tolerate some junk before the status line
+            mHttpResponseMatched = PR_TRUE;
+            char *p = LocateHttpStart(buf, PR_MIN(count, 8), PR_TRUE);
+            if (!p) {
+                // Treat any 0.9 style response of a put as a failure.
+                if (mRequestHead->Method() == nsHttp::Put)
+                    return NS_ERROR_ABORT;
 
-            mResponseHead->ParseStatusLine("");
-            mHaveStatusLine = PR_TRUE;
-            mHaveAllHeaders = PR_TRUE;
-            return NS_OK;
+                mResponseHead->ParseStatusLine("");
+                mHaveStatusLine = PR_TRUE;
+                mHaveAllHeaders = PR_TRUE;
+                return NS_OK;
+            }
+            if (p > buf) {
+                // skip over the junk
+                *countRead = p - buf;
+                buf = p;
+            }
         }
-        if (p > buf) {
-            // skip over the junk
-            *countRead = p - buf;
-            buf = p;
+        else {
+            char *p = LocateHttpStart(buf, count, PR_FALSE);
+            if (p) {
+                *countRead = p - buf;
+                buf = p;
+                mHttpResponseMatched = PR_TRUE;
+            } else {
+                *countRead = count;
+                return NS_OK;
+            }
         }
     }
     // otherwise we can assume that we don't have a HTTP/0.9 response.
 
     while ((eol = static_cast<char *>(memchr(buf, '\n', count - *countRead))) != nsnull) {
         // found line in range [buf:eol]
         len = eol - buf + 1;
 
@@ -858,16 +918,17 @@ nsHttpTransaction::HandleContentStart()
 
         // looks like we should ignore this response, resetting...
         if (reset) {
             LOG(("resetting transaction's response head\n"));
             mHaveAllHeaders = PR_FALSE;
             mHaveStatusLine = PR_FALSE;
             mReceivedData = PR_FALSE;
             mSentData = PR_FALSE;
+            mHttpResponseMatched = PR_FALSE;
             mResponseHead->Reset();
             // wait to be called again...
             return NS_OK;
         }
 
         // check if this is a no-content response
         switch (mResponseHead->Status()) {
         case 204:
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -134,16 +134,18 @@ public:
     PRBool    SSLConnectFailed() { return mSSLConnectFailed; }
 
     // These methods may only be used by the connection manager.
     void    SetPriority(PRInt32 priority) { mPriority = priority; }
     PRInt32    Priority()                 { return mPriority; }
 
 private:
     nsresult Restart();
+    char    *LocateHttpStart(char *buf, PRUint32 len,
+                             PRBool aAllowPartialMatch);
     void     ParseLine(char *line);
     nsresult ParseLineSegment(char *seg, PRUint32 len);
     nsresult ParseHead(char *, PRUint32 count, PRUint32 *countRead);
     nsresult HandleContentStart();
     nsresult HandleContent(char *, PRUint32 count, PRUint32 *contentRead, PRUint32 *contentRemaining);
     nsresult ProcessData(char *, PRUint32, PRUint32 *);
     void     DeleteSelfOnConsumerThread();
 
@@ -198,15 +200,16 @@ private:
     PRUint32                        mResponseIsComplete : 1;
     PRUint32                        mDidContentStart    : 1;
     PRUint32                        mNoContent          : 1; // expecting an empty entity body
     PRUint32                        mSentData           : 1;
     PRUint32                        mReceivedData       : 1;
     PRUint32                        mStatusEventPending : 1;
     PRUint32                        mHasRequestBody     : 1;
     PRUint32                        mSSLConnectFailed   : 1;
+    PRUint32                        mHttpResponseMatched: 1;
 
     // mClosed           := transaction has been explicitly closed
     // mTransactionDone  := transaction ran to completion or was interrupted
     // mResponseComplete := transaction ran to completion
 };
 
 #endif // nsHttpTransaction_h__