From 92aaf5d2fc4323e9f6cdcf1de5d16703a015b139 Mon Sep 17 00:00:00 2001 From: Patrick McManus Date: Tue, 20 Mar 2012 13:11:32 -0400 Subject: [PATCH] bug 597684 Implement HTTP Assoc-req and Banned Pipelines on nsHttpConnectionInfo r=honzab --HG-- extra : rebase_source : 26243e1af9e0554553520dae435cc4dcfd551746 --- modules/libpref/src/init/all.js | 4 + netwerk/protocol/http/SpdySession.cpp | 18 +++ netwerk/protocol/http/nsAHttpTransaction.h | 10 +- netwerk/protocol/http/nsHttpAtomList.h | 1 + netwerk/protocol/http/nsHttpChannel.cpp | 111 ++++++++++++++++++ netwerk/protocol/http/nsHttpChannel.h | 1 + netwerk/protocol/http/nsHttpConnection.cpp | 7 +- .../protocol/http/nsHttpConnectionInfo.cpp | 21 ++++ netwerk/protocol/http/nsHttpConnectionInfo.h | 10 +- netwerk/protocol/http/nsHttpHandler.cpp | 8 ++ netwerk/protocol/http/nsHttpHandler.h | 2 + netwerk/protocol/http/nsHttpPipeline.cpp | 31 ++++- netwerk/protocol/http/nsHttpTransaction.cpp | 14 +++ netwerk/protocol/http/nsHttpTransaction.h | 1 + netwerk/test/unit/head_channels.js | 10 +- netwerk/test/unit/test_assoc.js | 90 ++++++++++++++ netwerk/test/unit/xpcshell.ini | 1 + 17 files changed, 328 insertions(+), 12 deletions(-) create mode 100644 netwerk/test/unit/test_assoc.js diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index b3abc2befd08..2e49c17d7bdb 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -811,6 +811,10 @@ pref("network.http.pipelining.maxrequests" , 4); // Prompt for 307 redirects pref("network.http.prompt-temp-redirect", true); +// If true generate CORRUPTED_CONTENT errors for entities that +// contain an invalid Assoc-Req response header +pref("network.http.assoc-req.enforce", false); + // On networks deploying QoS, it is recommended that these be lockpref()'d, // since inappropriate marking can easily overwhelm bandwidth reservations // for certain services (i.e. EF for VoIP, AF4x for interactive video, diff --git a/netwerk/protocol/http/SpdySession.cpp b/netwerk/protocol/http/SpdySession.cpp index dd757e6c04a1..1c0bbe4b59e0 100644 --- a/netwerk/protocol/http/SpdySession.cpp +++ b/netwerk/protocol/http/SpdySession.cpp @@ -2159,6 +2159,24 @@ SpdySession::PipelineDepthAvailable() return 0; } +nsresult +SpdySession::SetPipelinePosition(PRInt32 position) +{ + // This API is meant for pipelining, SpdySession's should be + // extended with AddStream() + + NS_ABORT_IF_FALSE(false, + "SpdySession::SetPipelinePosition() should not be called"); + + return NS_ERROR_NOT_IMPLEMENTED; +} + +PRInt32 +SpdySession::PipelinePosition() +{ + return 0; +} + //----------------------------------------------------------------------------- // Pass through methods of nsAHttpConnection //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/http/nsAHttpTransaction.h b/netwerk/protocol/http/nsAHttpTransaction.h index f419897d3afc..10a1a1389d77 100644 --- a/netwerk/protocol/http/nsAHttpTransaction.h +++ b/netwerk/protocol/http/nsAHttpTransaction.h @@ -123,6 +123,12 @@ public: // called to count the number of sub transactions that can be added virtual PRUint16 PipelineDepthAvailable() = 0; + + // Used to inform the connection that it is being used in a pipelined + // context. That may influence the handling of some errors. + // The value is the pipeline position. + virtual nsresult SetPipelinePosition(PRInt32) = 0; + virtual PRInt32 PipelinePosition() = 0; }; #define NS_DECL_NSAHTTPTRANSACTION \ @@ -144,7 +150,9 @@ public: PRUint32 Http1xTransactionCount(); \ nsresult TakeSubTransactions(nsTArray > &outTransactions); \ nsresult AddTransaction(nsAHttpTransaction *); \ - PRUint16 PipelineDepthAvailable(); + PRUint16 PipelineDepthAvailable(); \ + nsresult SetPipelinePosition(PRInt32); \ + PRInt32 PipelinePosition(); //----------------------------------------------------------------------------- // nsAHttpSegmentReader diff --git a/netwerk/protocol/http/nsHttpAtomList.h b/netwerk/protocol/http/nsHttpAtomList.h index 58fcbb0dce80..f634479a37d5 100644 --- a/netwerk/protocol/http/nsHttpAtomList.h +++ b/netwerk/protocol/http/nsHttpAtomList.h @@ -57,6 +57,7 @@ HTTP_ATOM(Accept_Ranges, "Accept-Ranges") HTTP_ATOM(Age, "Age") HTTP_ATOM(Allow, "Allow") HTTP_ATOM(Alternate_Protocol, "Alternate-Protocol") +HTTP_ATOM(Assoc_Req, "Assoc-Req") HTTP_ATOM(Authentication, "Authentication") HTTP_ATOM(Authorization, "Authorization") HTTP_ATOM(Cache_Control, "Cache-Control") diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 9a6485a81dd7..1d92195eb024 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -47,6 +47,7 @@ #include "nsHttpChannel.h" #include "nsHttpHandler.h" +#include "nsStandardURL.h" #include "nsIApplicationCacheService.h" #include "nsIApplicationCacheContainer.h" #include "nsIAuthInformation.h" @@ -70,6 +71,7 @@ #include "nsDOMError.h" #include "nsAlgorithm.h" #include "sampler.h" +#include "nsIConsoleService.h" using namespace mozilla; @@ -768,6 +770,10 @@ nsHttpChannel::CallOnStartRequest() rv = ApplyContentConversions(); if (NS_FAILED(rv)) return rv; + rv = EnsureAssocReq(); + if (NS_FAILED(rv)) + return rv; + // if this channel is for a download, close off access to the cache. if (mCacheEntry && mChannelIsForDownload) { mCacheEntry->Doom(); @@ -1712,6 +1718,111 @@ nsHttpChannel::Hash(const char *buf, nsACString &hash) return NS_OK; } +nsresult +nsHttpChannel::EnsureAssocReq() +{ + // Confirm Assoc-Req response header on pipelined transactions + // per draft-nottingham-http-pipeline-01.txt + // of the form: GET http://blah.com/foo/bar?qv + // return NS_OK as long as we don't find a violation + // (i.e. no header is ok, as are malformed headers, as are + // transactions that have not been pipelined (unless those have been + // opted in via pragma)) + + if (!mResponseHead) + return NS_OK; + + const char *assoc_val = mResponseHead->PeekHeader(nsHttp::Assoc_Req); + if (!assoc_val) + return NS_OK; + + if (!mTransaction || !mURI) + return NS_OK; + + if (!mTransaction->PipelinePosition()) { + // "Pragma: X-Verify-Assoc-Req" can be used to verify even non pipelined + // transactions. It is used by test harness. + + const char *pragma_val = mResponseHead->PeekHeader(nsHttp::Pragma); + if (!pragma_val || + !nsHttp::FindToken(pragma_val, "X-Verify-Assoc-Req", + HTTP_HEADER_VALUE_SEPS)) + return NS_OK; + } + + char *method = net_FindCharNotInSet(assoc_val, HTTP_LWS); + if (!method) + return NS_OK; + + bool equals; + char *endofmethod; + + assoc_val = nsnull; + endofmethod = net_FindCharInSet(method, HTTP_LWS); + if (endofmethod) + assoc_val = net_FindCharNotInSet(endofmethod, HTTP_LWS); + if (!assoc_val) + return NS_OK; + + // check the method + PRInt32 methodlen = PL_strlen(mRequestHead.Method().get()); + if ((methodlen != (endofmethod - method)) || + PL_strncmp(method, + mRequestHead.Method().get(), + endofmethod - method)) { + LOG((" Assoc-Req failure Method %s", method)); + if (mConnectionInfo) + mConnectionInfo->BanPipelining(); + + nsCOMPtr consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (consoleService) { + nsAutoString message + (NS_LITERAL_STRING("Failed Assoc-Req. Received ")); + AppendASCIItoUTF16( + mResponseHead->PeekHeader(nsHttp::Assoc_Req), + message); + message += NS_LITERAL_STRING(" expected method "); + AppendASCIItoUTF16(mRequestHead.Method().get(), message); + consoleService->LogStringMessage(message.get()); + } + + if (gHttpHandler->EnforceAssocReq()) + return NS_ERROR_CORRUPTED_CONTENT; + return NS_OK; + } + + // check the URL + nsCOMPtr assoc_url; + if (NS_FAILED(NS_NewURI(getter_AddRefs(assoc_url), assoc_val)) || + !assoc_url) + return NS_OK; + + mURI->Equals(assoc_url, &equals); + if (!equals) { + LOG((" Assoc-Req failure URL %s", assoc_val)); + if (mConnectionInfo) + mConnectionInfo->BanPipelining(); + + nsCOMPtr consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (consoleService) { + nsAutoString message + (NS_LITERAL_STRING("Failed Assoc-Req. Received ")); + AppendASCIItoUTF16( + mResponseHead->PeekHeader(nsHttp::Assoc_Req), + message); + message += NS_LITERAL_STRING(" expected URL "); + AppendASCIItoUTF16(mSpec.get(), message); + consoleService->LogStringMessage(message.get()); + } + + if (gHttpHandler->EnforceAssocReq()) + return NS_ERROR_CORRUPTED_CONTENT; + } + return NS_OK; +} + //----------------------------------------------------------------------------- // nsHttpChannel //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index f8e59fe1b7da..177cbfa97418 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -182,6 +182,7 @@ private: nsresult ContinueProcessFallback(nsresult); bool ResponseWouldVary(); void HandleAsyncAbort(); + nsresult EnsureAssocReq(); nsresult ContinueOnStartRequest1(nsresult); nsresult ContinueOnStartRequest2(nsresult); diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp index 8947f9ca7d1d..ecbe8deab0a5 100644 --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -736,8 +736,13 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans, } } mKeepAliveMask = mKeepAlive; - mConnInfo->SetSupportsPipelining(mSupportsPipelining); + // Update the pipelining status in the connection info object + // and also read it back. It is possible the ci status is + // locked to false if pipelining has been banned on this ci due to + // some kind of observed flaky behavior + mSupportsPipelining = mConnInfo->SetSupportsPipelining(mSupportsPipelining); + // if this connection is persistent, then the server may send a "Keep-Alive" // header specifying the maximum number of times the connection can be // reused as well as the maximum amount of time the connection can be idle diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.cpp b/netwerk/protocol/http/nsHttpConnectionInfo.cpp index d74420473199..3747122370d8 100644 --- a/netwerk/protocol/http/nsHttpConnectionInfo.cpp +++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp @@ -100,6 +100,27 @@ nsHttpConnectionInfo::Clone() const return clone; } +bool +nsHttpConnectionInfo::SupportsPipelining() +{ + return mSupportsPipelining; +} + +bool +nsHttpConnectionInfo::SetSupportsPipelining(bool support) +{ + if (!mBannedPipelining) + mSupportsPipelining = support; + return mSupportsPipelining; +} + +void +nsHttpConnectionInfo::BanPipelining() +{ + mBannedPipelining = true; + mSupportsPipelining = false; +} + bool nsHttpConnectionInfo::ShouldForceConnectMethod() { diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.h b/netwerk/protocol/http/nsHttpConnectionInfo.h index 747bde7858ed..8daab44b8bf9 100644 --- a/netwerk/protocol/http/nsHttpConnectionInfo.h +++ b/netwerk/protocol/http/nsHttpConnectionInfo.h @@ -61,6 +61,7 @@ public: , mProxyInfo(proxyInfo) , mUsingSSL(usingSSL) , mSupportsPipelining(false) + , mBannedPipelining(false) { LOG(("Creating nsHttpConnectionInfo @%x\n", this)); @@ -127,13 +128,13 @@ public: { mHashKey.SetCharAt(anon ? 'A' : '.', 2); } bool GetAnonymous() { return mHashKey.CharAt(2) == 'A'; } - bool SupportsPipelining() { return mSupportsPipelining; } - void SetSupportsPipelining(bool support) - { mSupportsPipelining = support; } - bool ShouldForceConnectMethod(); const nsCString &GetHost() { return mHost; } + bool SupportsPipelining(); + bool SetSupportsPipelining(bool support); + void BanPipelining(); + private: nsrefcnt mRef; nsCString mHashKey; @@ -143,6 +144,7 @@ private: bool mUsingHttpProxy; bool mUsingSSL; bool mSupportsPipelining; + bool mBannedPipelining; }; #endif // nsHttpConnectionInfo_h__ diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index dcdf729368bf..cd569859f74c 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -190,6 +190,7 @@ nsHttpHandler::nsHttpHandler() , mPhishyUserPassLength(1) , mQoSBits(0x00) , mPipeliningOverSSL(false) + , mEnforceAssocReq(false) , mInPrivateBrowsingMode(PRIVATE_BROWSING_UNKNOWN) , mLastUniqueID(NowInSeconds()) , mSessionStartTime(0) @@ -1103,6 +1104,13 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref) } } + if (PREF_CHANGED(HTTP_PREF("assoc-req.enforce"))) { + cVar = false; + rv = prefs->GetBoolPref(HTTP_PREF("assoc-req.enforce"), &cVar); + if (NS_SUCCEEDED(rv)) + mEnforceAssocReq = cVar; + } + // enable Persistent caching for HTTPS - bug#205921 if (PREF_CHANGED(BROWSER_PREF("disk_cache_ssl"))) { cVar = false; diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h index b1d02acb9e24..8c18d78f9ead 100644 --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -110,6 +110,7 @@ public: PRUint16 GetIdleSynTimeout() { return mIdleSynTimeout; } bool FastFallbackToIPv4() { return mFastFallbackToIPv4; } PRUint32 MaxSocketCount(); + bool EnforceAssocReq() { return mEnforceAssocReq; } bool IsPersistentHttpsCachingEnabled() { return mEnablePersistentHttpsCaching; } bool IsTelemetryEnabled() { return mTelemetryEnabled; } @@ -300,6 +301,7 @@ private: PRUint8 mQoSBits; bool mPipeliningOverSSL; + bool mEnforceAssocReq; // cached value of whether or not the browser is in private browsing mode. enum { diff --git a/netwerk/protocol/http/nsHttpPipeline.cpp b/netwerk/protocol/http/nsHttpPipeline.cpp index 067adb054a8f..245675e11e42 100644 --- a/netwerk/protocol/http/nsHttpPipeline.cpp +++ b/netwerk/protocol/http/nsHttpPipeline.cpp @@ -131,11 +131,20 @@ nsHttpPipeline::AddTransaction(nsAHttpTransaction *trans) NS_ADDREF(trans); mRequestQ.AppendElement(trans); + PRInt32 qlen = mRequestQ.Length(); + + if (qlen != 1) { + trans->SetPipelinePosition(qlen); + } + else { + // do it for this case in case an idempotent cancellation + // is being repeated and an old value needs to be cleared + trans->SetPipelinePosition(0); + } if (mConnection && !mClosed) { trans->SetConnection(this); - - if (mRequestQ.Length() == 1) + if (qlen == 1) mConnection->ResumeSend(); } @@ -163,6 +172,24 @@ nsHttpPipeline::PipelineDepthAvailable() return mMaxPipelineDepth - currentTransactions; } +nsresult +nsHttpPipeline::SetPipelinePosition(PRInt32 position) +{ + nsAHttpTransaction *trans = Response(0); + if (trans) + return trans->SetPipelinePosition(position); + return NS_OK; +} + +PRInt32 +nsHttpPipeline::PipelinePosition() +{ + nsAHttpTransaction *trans = Response(0); + if (trans) + return trans->PipelinePosition(); + return 2; +} + //----------------------------------------------------------------------------- // nsHttpPipeline::nsISupports //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp index 873717a9edab..22a03ebaf391 100644 --- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -119,6 +119,7 @@ nsHttpTransaction::nsHttpTransaction() , mPriority(0) , mRestartCount(0) , mCaps(0) + , mPipelinePosition(0) , mClosed(false) , mConnected(false) , mHaveStatusLine(false) @@ -722,6 +723,19 @@ nsHttpTransaction::PipelineDepthAvailable() return 0; } +nsresult +nsHttpTransaction::SetPipelinePosition(PRInt32 position) +{ + mPipelinePosition = position; + return NS_OK; +} + +PRInt32 +nsHttpTransaction::PipelinePosition() +{ + return mPipelinePosition; +} + //----------------------------------------------------------------------------- // nsHttpTransaction //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/http/nsHttpTransaction.h b/netwerk/protocol/http/nsHttpTransaction.h index 1b8e686ce24b..f071b7dc2a33 100644 --- a/netwerk/protocol/http/nsHttpTransaction.h +++ b/netwerk/protocol/http/nsHttpTransaction.h @@ -199,6 +199,7 @@ private: PRUint16 mRestartCount; // the number of times this transaction has been restarted PRUint8 mCaps; + PRInt32 mPipelinePosition; // state flags, all logically boolean, but not packed together into a // bitfield so as to avoid bitfield-induced races. See bug 560579. diff --git a/netwerk/test/unit/head_channels.js b/netwerk/test/unit/head_channels.js index 69bf2c99ff94..42002a39d312 100644 --- a/netwerk/test/unit/head_channels.js +++ b/netwerk/test/unit/head_channels.js @@ -26,6 +26,7 @@ const CL_EXPECT_GZIP = 0x2; const CL_EXPECT_3S_DELAY = 0x4; const CL_SUSPEND = 0x8; const CL_ALLOW_UNKNOWN_CL = 0x10; +const CL_EXPECT_LATE_FAILURE = 0x20; const SUSPEND_DELAY = 3000; @@ -38,7 +39,8 @@ const SUSPEND_DELAY = 3000; * * This listener makes sure that various parts of the channel API are * implemented correctly and that the channel's status is a success code - * (you can pass CL_EXPECT_FAILURE as flags to allow a failure code) + * (you can pass CL_EXPECT_FAILURE or CL_EXPECT_LATE_FAILURE as flags + * to allow a failure code) * * Note that it also requires a valid content length on the channel and * is thus not fully generic. @@ -131,15 +133,15 @@ ChannelListener.prototype = { if (this._got_onstoprequest) do_throw("Got second onStopRequest event!"); this._got_onstoprequest = true; - if ((this._flags & CL_EXPECT_FAILURE) && success) + if ((this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && success) do_throw("Should have failed to load URL (status is " + status.toString(16) + ")"); - else if (!(this._flags & CL_EXPECT_FAILURE) && !success) + else if (!(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_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) && + if (!(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && !(this._flags & CL_EXPECT_GZIP) && this._contentLen != -1) do_check_eq(this._buffer.length, this._contentLen) diff --git a/netwerk/test/unit/test_assoc.js b/netwerk/test/unit/test_assoc.js new file mode 100644 index 000000000000..c1f4087447b2 --- /dev/null +++ b/netwerk/test/unit/test_assoc.js @@ -0,0 +1,90 @@ +do_load_httpd_js(); + +var httpserver = new nsHttpServer(); +var currentTestIndex = 0; +var tests = [ + // this is valid + {url: "/assoc/assoctest?valid", + responseheader: [ "Assoc-Req: GET http://localhost:4444/assoc/assoctest?valid", + "Pragma: X-Verify-Assoc-Req" ], + flags : 0}, + + // this is invalid because the method is wrong + {url: "/assoc/assoctest?invalid", + responseheader: [ "Assoc-Req: POST http://localhost:4444/assoc/assoctest?invalid", + "Pragma: X-Verify-Assoc-Req" ], + flags : CL_EXPECT_LATE_FAILURE}, + + // this is invalid because the url is wrong + {url: "/assoc/assoctest?notvalid", + responseheader: [ "Assoc-Req: GET http://localhost:4444/wrongpath/assoc/assoctest?notvalid", + "Pragma: X-Verify-Assoc-Req" ], + flags : CL_EXPECT_LATE_FAILURE}, + + // this is invalid because the space between method and URL is missing + {url: "/assoc/assoctest?invalid2", + responseheader: [ "Assoc-Req: GEThttp://localhost:4444/assoc/assoctest?invalid2", + "Pragma: X-Verify-Assoc-Req" ], + flags : CL_EXPECT_LATE_FAILURE}, +]; + +var oldPrefVal; +var domBranch; + +function setupChannel(url) +{ + var ios = Components.classes["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + var chan = ios.newChannel("http://localhost:4444" + url, "", null); + return chan; +} + +function startIter() +{ + var channel = setupChannel(tests[currentTestIndex].url); + channel.asyncOpen(new ChannelListener(completeIter, + channel, tests[currentTestIndex].flags), null); +} + +function completeIter(request, data, ctx) +{ + if (++currentTestIndex < tests.length ) { + startIter(); + } else { + domBranch.setBoolPref("enforce", oldPrefVal); + httpserver.stop(do_test_finished); + } +} + +function run_test() +{ + var prefService = + Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefService); + domBranch = prefService.getBranch("network.http.assoc-req."); + oldPrefVal = domBranch.getBoolPref("enforce"); + domBranch.setBoolPref("enforce", true); + + httpserver.registerPathHandler("/assoc/assoctest", handler); + httpserver.start(4444); + + startIter(); + do_test_pending(); +} + +function handler(metadata, response) +{ + var body = "thequickbrownfox"; + response.setHeader("Content-Type", "text/plain", false); + + var header = tests[currentTestIndex].responseheader; + if (header != undefined) { + for (var i = 0; i < header.length; i++) { + var splitHdr = header[i].split(": "); + response.setHeader(splitHdr[0], splitHdr[1], false); + } + } + + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.bodyOutputStream.write(body, body.length); +} diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini index 9d61663e63ae..204cc1ad9737 100644 --- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -6,6 +6,7 @@ tail = [test_NetUtil.js] [test_URIs.js] [test_aboutblank.js] +[test_assoc.js] [test_auth_proxy.js] [test_authentication.js] # Bug 675039: test hangs consistently on Android