Bug 237623 - Introduce "soft" checks for HTTP framing errors. r=mcmanus

This commit is contained in:
Daniel Stenberg 2015-02-11 05:17:00 -05:00
parent 0ca5e8f187
commit 9f8d9fe372
9 changed files with 73 additions and 21 deletions

View File

@ -1349,7 +1349,8 @@ pref("network.http.tcp_keepalive.short_lived_idle_time", 10);
pref("network.http.tcp_keepalive.long_lived_connections", true);
pref("network.http.tcp_keepalive.long_lived_idle_time", 600);
pref("network.http.enforce-framing.http1", false);
pref("network.http.enforce-framing.http1", false); // should be named "strict"
pref("network.http.enforce-framing.soft", true);
// default values for FTP
// in a DSCP environment this should be 40 (0x28, or AF11), per RFC-4594,

View File

@ -208,7 +208,7 @@ nsHttpHandler::nsHttpHandler()
, mTCPKeepaliveShortLivedIdleTimeS(10)
, mTCPKeepaliveLongLivedEnabled(false)
, mTCPKeepaliveLongLivedIdleTimeS(600)
, mEnforceH1Framing(false)
, mEnforceH1Framing(FRAMECHECK_BARELY)
{
#if defined(PR_LOGGING)
gHttpLog = PR_NewLogModule("nsHttp");
@ -1524,10 +1524,18 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
1, kMaxTCPKeepIdle);
}
if (PREF_CHANGED(HTTP_PREF("enforce-framing.http1"))) {
if (PREF_CHANGED(HTTP_PREF("enforce-framing.http1")) ||
PREF_CHANGED(HTTP_PREF("enforce-framing.soft")) ) {
rv = prefs->GetBoolPref(HTTP_PREF("enforce-framing.http1"), &cVar);
if (NS_SUCCEEDED(rv)) {
mEnforceH1Framing = cVar;
if (NS_SUCCEEDED(rv) && cVar) {
mEnforceH1Framing = FRAMECHECK_STRICT;
} else {
rv = prefs->GetBoolPref(HTTP_PREF("enforce-framing.soft"), &cVar);
if (NS_SUCCEEDED(rv) && cVar) {
mEnforceH1Framing = FRAMECHECK_BARELY;
} else {
mEnforceH1Framing = FRAMECHECK_LAX;
}
}
}

View File

@ -39,6 +39,12 @@ class nsHttpConnectionInfo;
class nsHttpTransaction;
class AltSvcMapping;
enum FrameCheckLevel {
FRAMECHECK_LAX,
FRAMECHECK_BARELY,
FRAMECHECK_STRICT
};
//-----------------------------------------------------------------------------
// nsHttpHandler - protocol handler for HTTP and HTTPS
//-----------------------------------------------------------------------------
@ -151,8 +157,9 @@ public:
return mTCPKeepaliveLongLivedIdleTimeS;
}
// returns the network.http.enforce-framing.http1 preference
bool GetEnforceH1Framing() { return mEnforceH1Framing; }
// returns the HTTP framing check level preference, as controlled with
// network.http.enforce-framing.http1 and network.http.enforce-framing.soft
FrameCheckLevel GetEnforceH1Framing() { return mEnforceH1Framing; }
nsHttpAuthCache *AuthCache(bool aPrivate) {
return aPrivate ? &mPrivateAuthCache : &mAuthCache;
@ -531,7 +538,7 @@ private:
// if true, generate NS_ERROR_PARTIAL_TRANSFER for h1 responses with
// incorrect content lengths or malformed chunked encodings
bool mEnforceH1Framing;
FrameCheckLevel mEnforceH1Framing;
private:
// For Rate Pacing Certain Network Events. Only assign this pointer on

View File

@ -126,6 +126,8 @@ nsHttpTransaction::nsHttpTransaction()
, mDontRouteViaWildCard(false)
, mForceRestart(false)
, mReuseOnRestart(false)
, mContentDecoding(false)
, mContentDecodingCheck(false)
, mReportedStart(false)
, mReportedResponseHeader(false)
, mForTakeResponseHead(nullptr)
@ -909,11 +911,17 @@ nsHttpTransaction::Close(nsresult reason)
NS_WARNING("Partial transfer, incomplete HTTP response received");
if ((mHttpVersion >= NS_HTTP_VERSION_1_1) &&
gHttpHandler->GetEnforceH1Framing()) {
reason = NS_ERROR_NET_PARTIAL_TRANSFER;
LOG(("Partial transfer, incomplete HTTP response received: %s",
mChunkedDecoder ? "broken chunk" : "c-l underrun"));
if (mHttpVersion >= NS_HTTP_VERSION_1_1) {
FrameCheckLevel clevel = gHttpHandler->GetEnforceH1Framing();
if (clevel >= FRAMECHECK_BARELY) {
if ((clevel == FRAMECHECK_STRICT) ||
(mChunkedDecoder && mChunkedDecoder->GetChunkRemaining()) ||
(!mChunkedDecoder && !mContentDecoding && mContentDecodingCheck) ) {
reason = NS_ERROR_NET_PARTIAL_TRANSFER;
LOG(("Partial transfer, incomplete HTTP response received: %s",
mChunkedDecoder ? "broken chunk" : "c-l underrun"));
}
}
}
if (mConnection) {
@ -1736,6 +1744,12 @@ nsHttpTransaction::ProcessData(char *buf, uint32_t count, uint32_t *countRead)
MOZ_ASSERT(mConnection);
mConnection->PushBack(buf + *countRead, countRemaining);
}
if (!mContentDecodingCheck && mResponseHead) {
mContentDecoding =
!!mResponseHead->PeekHeader(nsHttp::Content_Encoding);
mContentDecodingCheck = true;
}
}
return NS_OK;

View File

@ -282,6 +282,8 @@ private:
bool mDontRouteViaWildCard;
bool mForceRestart;
bool mReuseOnRestart;
bool mContentDecoding;
bool mContentDecodingCheck;
// mClosed := transaction has been explicitly closed
// mTransactionDone := transaction ran to completion or was interrupted

View File

@ -12,6 +12,8 @@
#include "nsStreamUtils.h"
#include "nsStringStream.h"
#include "nsComponentManagerUtils.h"
#include "nsThreadUtils.h"
#include "mozilla/Preferences.h"
// nsISupports implementation
NS_IMPL_ISUPPORTS(nsHTTPCompressConv,
@ -35,6 +37,13 @@ nsHTTPCompressConv::nsHTTPCompressConv()
, mSkipCount(0)
, mFlags(0)
{
if (NS_IsMainThread()) {
mFailUncleanStops =
(Preferences::GetBool("network.http.enforce-framing.soft", false) ||
Preferences::GetBool("network.http.enforce-framing.http", false));
} else {
mFailUncleanStops = false;
}
}
nsHTTPCompressConv::~nsHTTPCompressConv()
@ -88,6 +97,11 @@ NS_IMETHODIMP
nsHTTPCompressConv::OnStopRequest(nsIRequest* request, nsISupports *aContext,
nsresult aStatus)
{
if (!mStreamEnded && NS_SUCCEEDED(aStatus) && mFailUncleanStops) {
// This is not a clean end of stream, the transfer is incomplete.
aStatus = NS_ERROR_NET_PARTIAL_TRANSFER;
}
return mListener->OnStopRequest(request, aContext, aStatus);
}

View File

@ -76,6 +76,7 @@ private:
bool mStreamEnded;
bool mStreamInitialized;
bool mDummyStreamInitialised;
bool mFailUncleanStops;
z_stream d_stream;
unsigned mLen, hMode, mSkipCount, mFlags;

View File

@ -18,12 +18,14 @@ var test_flags = new Array();
var testPathBase = "/cl_hdrs";
var prefs;
var enforcePref;
var enforcePrefStrict;
var enforcePrefSoft;
function run_test()
{
prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
enforcePref = prefs.getBoolPref("network.http.enforce-framing.http1");
enforcePrefStrict = prefs.getBoolPref("network.http.enforce-framing.http1");
enforcePrefSoft = prefs.getBoolPref("network.http.enforce-framing.soft");
prefs.setBoolPref("network.http.enforce-framing.http1", true);
httpserver.start(-1);
@ -61,7 +63,9 @@ function setupChannel(url)
function endTests()
{
prefs.setBoolPref("network.http.enforce-framing.http1", enforcePref);
// restore the prefs to pre-test values
prefs.setBoolPref("network.http.enforce-framing.http1", enforcePrefStrict);
prefs.setBoolPref("network.http.enforce-framing.soft", enforcePrefSoft);
httpserver.stop(do_test_finished);
}
@ -111,8 +115,9 @@ function completeTest2(request, data, ctx)
{
do_check_eq(request.status, Components.results.NS_OK);
// test 3 requires the pref to be false
// test 3 requires the enforce-framing prefs to be false
prefs.setBoolPref("network.http.enforce-framing.http1", false);
prefs.setBoolPref("network.http.enforce-framing.soft", false);
run_test_number(3);
}
@ -135,7 +140,7 @@ function handler3(metadata, response)
function completeTest3(request, data, ctx)
{
// reset the pref in case we add more tests
// reset the strict pref in case we add more tests
prefs.setBoolPref("network.http.enforce-framing.http1", true);
do_check_eq(request.status, Components.results.NS_OK);
endTests();

View File

@ -63,8 +63,8 @@ var enforcePref;
function run_test()
{
enforcePref = Services.prefs.getBoolPref("network.http.enforce-framing.http1");
Services.prefs.setBoolPref("network.http.enforce-framing.http1", false);
enforcePref = Services.prefs.getBoolPref("network.http.enforce-framing.soft");
Services.prefs.setBoolPref("network.http.enforce-framing.soft", false);
httpServer = new HttpServer();
httpServer.registerPathHandler("/content", contentHandler);
@ -87,6 +87,6 @@ function firstTimeThrough(request, buffer)
function finish_test(request, buffer)
{
do_check_eq(buffer, responseBody);
Services.prefs.setBoolPref("network.http.enforce-framing.http1", enforcePref);
Services.prefs.setBoolPref("network.http.enforce-framing.soft", enforcePref);
httpServer.stop(do_test_finished);
}