From 05af16a23abe37210e0b880aa27387d8994419dd Mon Sep 17 00:00:00 2001
From: Monty Brandenberg <monty@lindenlab.com>
Date: Wed, 6 Jun 2012 13:52:38 -0400
Subject: [PATCH] Policy + caching fixes + https support + POST working
 Implemented first global policy definitions to support SSL CA certificate
 configuration to support https: operations.  Fixed HTTP 206 status handling
 to match what is currently being done by grid services and to lay a
 foundation for fixes that will be a response to ER-1824.  More libcurl
 CURLOPT options set on easy handles to do peer verification in the
 traditional way.  HTTP POST working and now reporting asset metrics back to
 grid for the viewer's asset system.  This uses LLSD so that is also showing
 as compatible with the new library.

---
 indra/llcorehttp/CMakeLists.txt     |  2 ++
 indra/llcorehttp/_httplibcurl.cpp   |  1 +
 indra/llcorehttp/_httpoprequest.cpp | 45 +++++++++++++++++++++++++----
 indra/llcorehttp/_httppolicy.cpp    |  4 +--
 indra/llcorehttp/_httppolicy.h      | 10 +++++++
 indra/llcorehttp/_httpservice.h     |  8 ++---
 indra/llcorehttp/httpcommon.cpp     |  5 +++-
 indra/llcorehttp/httpcommon.h       | 11 ++++++-
 indra/llcorehttp/httprequest.cpp    | 13 +++++++--
 indra/llcorehttp/httprequest.h      | 16 +++++-----
 indra/newview/llappviewer.cpp       | 12 +++++++-
 indra/newview/lltexturefetch.cpp    | 37 ++++++++++++++----------
 12 files changed, 126 insertions(+), 38 deletions(-)

diff --git a/indra/llcorehttp/CMakeLists.txt b/indra/llcorehttp/CMakeLists.txt
index 9a073eb8501..3fda524ddfb 100644
--- a/indra/llcorehttp/CMakeLists.txt
+++ b/indra/llcorehttp/CMakeLists.txt
@@ -33,6 +33,7 @@ set(llcorehttp_SOURCE_FILES
     _httpoprequest.cpp
     _httpopsetpriority.cpp
     _httppolicy.cpp
+    _httppolicyglobal.cpp
     _httpreplyqueue.cpp
     _httprequestqueue.cpp
     _httpservice.cpp
@@ -55,6 +56,7 @@ set(llcorehttp_HEADER_FILES
     _httpoprequest.h
     _httpopsetpriority.h
     _httppolicy.h
+    _httppolicyglobal.h
     _httpreadyqueue.h
     _httpreplyqueue.h
     _httprequestqueue.h
diff --git a/indra/llcorehttp/_httplibcurl.cpp b/indra/llcorehttp/_httplibcurl.cpp
index 704f9baac91..5272c391e8e 100644
--- a/indra/llcorehttp/_httplibcurl.cpp
+++ b/indra/llcorehttp/_httplibcurl.cpp
@@ -27,6 +27,7 @@
 #include "_httplibcurl.h"
 
 #include "httpheaders.h"
+#include "bufferarray.h"
 #include "_httpoprequest.h"
 
 
diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp
index f52ff5a44c4..4bdc4a5257c 100644
--- a/indra/llcorehttp/_httpoprequest.cpp
+++ b/indra/llcorehttp/_httpoprequest.cpp
@@ -40,8 +40,10 @@
 #include "_httpreplyqueue.h"
 #include "_httpservice.h"
 #include "_httppolicy.h"
+#include "_httppolicyglobal.h"
 #include "_httplibcurl.h"
 
+#include "llhttpstatuscodes.h"
 
 namespace
 {
@@ -153,14 +155,14 @@ HttpOpRequest::~HttpOpRequest()
 void HttpOpRequest::stageFromRequest(HttpService * service)
 {
 	addRef();
-	service->getPolicy()->addOp(this);			// transfers refcount
+	service->getPolicy().addOp(this);			// transfers refcount
 }
 
 
 void HttpOpRequest::stageFromReady(HttpService * service)
 {
 	addRef();
-	service->getTransport()->addOp(this);		// transfers refcount
+	service->getTransport().addOp(this);		// transfers refcount
 }
 
 
@@ -195,6 +197,8 @@ void HttpOpRequest::stageFromActive(HttpService * service)
 
 void HttpOpRequest::visitNotifier(HttpRequest * request)
 {
+	static const HttpStatus partial_content(HTTP_PARTIAL_CONTENT, HE_SUCCESS);
+	
 	if (mLibraryHandler)
 	{
 		HttpResponse * response = new HttpResponse();
@@ -208,9 +212,15 @@ void HttpOpRequest::visitNotifier(HttpRequest * request)
 			offset = mReplyOffset;
 			length = mReplyLength;
 		}
-		else if (mReplyBody)
+		else if (mReplyBody && partial_content == mStatus)
 		{
-			// Provide implicit offset/length from request/response
+			// Legacy grid services did not provide a 'Content-Range'
+			// header in responses to full- or partly-satisfyiable
+			// 'Range' requests.  For these, we have to hope that
+			// the data starts where requested and the length is simply
+			// whatever we received.  A bit of sanity could be provided
+			// by overlapping ranged requests and verifying that the
+			// overlap matches.
 			offset = mReqOffset;
 			length = mReplyBody->size();
 		}
@@ -306,6 +316,9 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
 	// *FIXME:  better error handling later
 	HttpStatus status;
 
+	// Get policy options
+	HttpPolicyGlobal & policy(service->getPolicy().getGlobalOptions());
+	
 	mCurlHandle = curl_easy_init();
 	// curl_easy_setopt(mCurlHandle, CURLOPT_VERBOSE, 1);
 	curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, 30);
@@ -322,21 +335,40 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
 	curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 0);
 	curl_easy_setopt(mCurlHandle, CURLOPT_AUTOREFERER, 1);
 	curl_easy_setopt(mCurlHandle, CURLOPT_FOLLOWLOCATION, 1);
-	curl_easy_setopt(mCurlHandle, CURLOPT_MAXREDIRS, 10);
+	curl_easy_setopt(mCurlHandle, CURLOPT_MAXREDIRS, 10);		// *FIXME:  parameterize this later
 	curl_easy_setopt(mCurlHandle, CURLOPT_WRITEFUNCTION, writeCallback);
 	curl_easy_setopt(mCurlHandle, CURLOPT_WRITEDATA, mCurlHandle);
 	curl_easy_setopt(mCurlHandle, CURLOPT_READFUNCTION, readCallback);
 	curl_easy_setopt(mCurlHandle, CURLOPT_READDATA, mCurlHandle);
+	curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYPEER, 1);
+	curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYHOST, 0);
 
+	std::string opt_value;
+	if (policy.get(HttpRequest::GP_CA_PATH, opt_value))
+	{
+		curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, opt_value.c_str());
+	}
+	if (policy.get(HttpRequest::GP_CA_FILE, opt_value))
+	{
+		curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, opt_value.c_str());
+	}
+	if (policy.get(HttpRequest::GP_HTTP_PROXY, opt_value))
+	{
+		curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, opt_value.c_str());
+	}
+	
 	switch (mReqMethod)
 	{
 	case HOR_GET:
 		curl_easy_setopt(mCurlHandle, CURLOPT_HTTPGET, 1);
+		mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
+		mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
 		break;
 		
 	case HOR_POST:
 		{
 			curl_easy_setopt(mCurlHandle, CURLOPT_POST, 1);
+			curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, "");
 			long data_size(0);
 			if (mReqBody)
 			{
@@ -358,8 +390,11 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
 				data_size = mReqBody->size();
 			}
 			curl_easy_setopt(mCurlHandle, CURLOPT_INFILESIZE, data_size);
+			curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, (void *) NULL);
 			mCurlHeaders = curl_slist_append(mCurlHeaders, "Transfer-Encoding: chunked");
 			mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:");
+			mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
+			mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
 		}
 		break;
 		
diff --git a/indra/llcorehttp/_httppolicy.cpp b/indra/llcorehttp/_httppolicy.cpp
index 1d28f23d563..51f5e487dc8 100644
--- a/indra/llcorehttp/_httppolicy.cpp
+++ b/indra/llcorehttp/_httppolicy.cpp
@@ -76,12 +76,12 @@ void HttpPolicy::addOp(HttpOpRequest * op)
 HttpService::ELoopSpeed HttpPolicy::processReadyQueue()
 {
 	HttpService::ELoopSpeed result(HttpService::REQUEST_SLEEP);
-	HttpLibcurl * pTransport(mService->getTransport());
+	HttpLibcurl & transport(mService->getTransport());
 	
 	for (int policy_class(0); policy_class < HttpRequest::POLICY_CLASS_LIMIT; ++policy_class)
 	{
 		HttpReadyQueue & readyq(mReadyQueue[policy_class]);
-		int active(pTransport->getActiveCountInClass(policy_class));
+		int active(transport.getActiveCountInClass(policy_class));
 		int needed(8 - active);
 
 		if (needed > 0 && mReadyInClass[policy_class] > 0)
diff --git a/indra/llcorehttp/_httppolicy.h b/indra/llcorehttp/_httppolicy.h
index 2bc03c531fb..425079ec637 100644
--- a/indra/llcorehttp/_httppolicy.h
+++ b/indra/llcorehttp/_httppolicy.h
@@ -31,6 +31,7 @@
 #include "httprequest.h"
 #include "_httpservice.h"
 #include "_httpreadyqueue.h"
+#include "_httppolicyglobal.h"
 
 
 namespace LLCore
@@ -68,11 +69,20 @@ class HttpPolicy
 
 	// Shadows HttpService's method
 	bool changePriority(HttpHandle handle, HttpRequest::priority_t priority);
+
+	// Get pointer to global policy options.  Caller is expected
+	// to do context checks like no setting once running.
+	HttpPolicyGlobal &	getGlobalOptions()
+		{
+			return mGlobalOptions;
+		}
+	
 	
 protected:
 	int					mReadyInClass[HttpRequest::POLICY_CLASS_LIMIT];
 	HttpReadyQueue		mReadyQueue[HttpRequest::POLICY_CLASS_LIMIT];
 	HttpService *		mService;				// Naked pointer, not refcounted, not owner
+	HttpPolicyGlobal	mGlobalOptions;
 	
 };  // end class HttpPolicy
 
diff --git a/indra/llcorehttp/_httpservice.h b/indra/llcorehttp/_httpservice.h
index 095316c8a77..748354a8e49 100644
--- a/indra/llcorehttp/_httpservice.h
+++ b/indra/llcorehttp/_httpservice.h
@@ -148,14 +148,14 @@ class HttpService
 	/// Threading:  callable by worker thread.
 	bool changePriority(HttpHandle handle, HttpRequest::priority_t priority);
 	
-	HttpPolicy * getPolicy()
+	HttpPolicy & getPolicy()
 		{
-			return mPolicy;
+			return *mPolicy;
 		}
 
-	HttpLibcurl * getTransport()
+	HttpLibcurl & getTransport()
 		{
-			return mTransport;
+			return *mTransport;
 		}
 	
 protected:
diff --git a/indra/llcorehttp/httpcommon.cpp b/indra/llcorehttp/httpcommon.cpp
index a01182cf231..9f17b5c8426 100644
--- a/indra/llcorehttp/httpcommon.cpp
+++ b/indra/llcorehttp/httpcommon.cpp
@@ -66,7 +66,10 @@ std::string HttpStatus::toString() const
 			"Services shutting down",
 			"Operation canceled",
 			"Invalid Content-Range header encountered",
-			"Request handle not found"
+			"Request handle not found",
+			"Invalid datatype for argument or option",
+			"Option has not been explicitly set",
+			"Option is not dynamic and must be set early"
 		};
 	static const int llcore_errors_count(sizeof(llcore_errors) / sizeof(llcore_errors[0]));
 
diff --git a/indra/llcorehttp/httpcommon.h b/indra/llcorehttp/httpcommon.h
index c01a5f85d30..fd2661b700c 100644
--- a/indra/llcorehttp/httpcommon.h
+++ b/indra/llcorehttp/httpcommon.h
@@ -137,7 +137,16 @@ enum HttpError
 	HE_INV_CONTENT_RANGE_HDR = 4,
 	
 	// Request handle not found
-	HE_HANDLE_NOT_FOUND = 5
+	HE_HANDLE_NOT_FOUND = 5,
+
+	// Invalid datatype for option/setting
+	HE_INVALID_ARG = 6,
+
+	// Option hasn't been explicitly set
+	HE_OPT_NOT_SET = 7,
+	
+	// Option not dynamic, must be set during init phase
+	HE_OPT_NOT_DYNAMIC = 8
 	
 }; // end enum HttpError
 
diff --git a/indra/llcorehttp/httprequest.cpp b/indra/llcorehttp/httprequest.cpp
index 0e512d97ed5..baa0fe1a842 100644
--- a/indra/llcorehttp/httprequest.cpp
+++ b/indra/llcorehttp/httprequest.cpp
@@ -29,6 +29,7 @@
 #include "_httprequestqueue.h"
 #include "_httpreplyqueue.h"
 #include "_httpservice.h"
+#include "_httppolicy.h"
 #include "_httpoperation.h"
 #include "_httpoprequest.h"
 #include "_httpopsetpriority.h"
@@ -127,9 +128,17 @@ HttpRequest::~HttpRequest()
 
 HttpStatus HttpRequest::setPolicyGlobalOption(EGlobalPolicy opt, long value)
 {
-	HttpStatus status;
+	// *FIXME:  Fail if thread is running.
 
-	return status;
+	return HttpService::instanceOf()->getPolicy().getGlobalOptions().set(opt, value);
+}
+
+
+HttpStatus HttpRequest::setPolicyGlobalOption(EGlobalPolicy opt, const std::string & value)
+{
+	// *FIXME:  Fail if thread is running.
+
+	return HttpService::instanceOf()->getPolicy().getGlobalOptions().set(opt, value);
 }
 
 
diff --git a/indra/llcorehttp/httprequest.h b/indra/llcorehttp/httprequest.h
index 57d2da245b6..3592d5c6a37 100644
--- a/indra/llcorehttp/httprequest.h
+++ b/indra/llcorehttp/httprequest.h
@@ -111,7 +111,10 @@ class HttpRequest
 		/// Maximum number of connections the library will use to
 		/// perform operations.  This is somewhat soft as the underlying
 		/// transport will cache some connections (up to 5).
-		GLOBAL_CONNECTION_LIMIT
+		GP_CONNECTION_LIMIT,		///< Takes long giving number of connections
+		GP_CA_PATH,					///< System path/directory where SSL certs are stored.
+		GP_CA_FILE,					///< System path/file containing certs.
+		GP_HTTP_PROXY				///< String giving host/port to use for HTTP proxy
 	};
 
 	/// Set a parameter on a global policy option.  Calls
@@ -122,6 +125,7 @@ class HttpRequest
 	/// @param value	Desired value of option.
 	/// @return			Standard status code.
 	HttpStatus setPolicyGlobalOption(EGlobalPolicy opt, long value);
+	HttpStatus setPolicyGlobalOption(EGlobalPolicy opt, const std::string & value);
 
 	/// Create a new policy class into which requests can be made.
 	///
@@ -134,15 +138,15 @@ class HttpRequest
 	enum EClassPolicy
 	{
 		/// Limits the number of connections used for the class.
-		CLASS_CONNECTION_LIMIT,
+		CP_CONNECTION_LIMIT,
 
 		/// Limits the number of connections used for a single
 		/// literal address/port pair within the class.
-		PER_HOST_CONNECTION_LIMIT,
+		CP_PER_HOST_CONNECTION_LIMIT,
 
 		/// Suitable requests are allowed to pipeline on their
 		/// connections when they ask for it.
-		ENABLE_PIPELINING
+		CP_ENABLE_PIPELINING
 	};
 	
 	/// Set a parameter on a class-based policy option.  Calls
@@ -153,9 +157,7 @@ class HttpRequest
 	/// @param opt				Enum of option to be set.
 	/// @param value			Desired value of option.
 	/// @return					Standard status code.
-	HttpStatus setPolicyClassOption(policy_t policy_id,
-									EClassPolicy opt,
-									long value);
+	HttpStatus setPolicyClassOption(policy_t policy_id, EClassPolicy opt, long value);
 
 	/// @}
 
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 8e6deb9ccee..7a44415fba9 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -5356,6 +5356,17 @@ void CoreHttp::init()
 						<< LL_ENDL;
 	}
 
+	mRequest = new LLCore::HttpRequest;
+
+	status = mRequest->setPolicyGlobalOption(LLCore::HttpRequest::GP_CA_FILE,
+											 gDirUtilp->getCAFile());
+	if (! status)
+	{
+		LL_ERRS("Init") << "Failed to set CA File for HTTP services.  Reason:  "
+						<< status.toString()
+						<< LL_ENDL;
+	}
+	
 	status = LLCore::HttpRequest::startThread();
 	if (! status)
 	{
@@ -5364,7 +5375,6 @@ void CoreHttp::init()
 						<< LL_ENDL;
 	}
 
-	mRequest = new LLCore::HttpRequest;
 }
 
 
diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp
index 34fb21798fb..f9294b4cd1f 100644
--- a/indra/newview/lltexturefetch.cpp
+++ b/indra/newview/lltexturefetch.cpp
@@ -816,14 +816,6 @@ bool LLTextureFetchWorker::doWork(S32 param)
 		mFetchTimer.reset();
 	}
 
-	static LLUUID last_id;
-	if (mID != last_id)
-	{
-		// LL_WARNS("Texture") << "DOWORK SWITCH: " << last_id << " to: " << mID
-		// << LL_ENDL;
-		last_id = mID;
-	}
-	
 	if (mState == INIT)
 	{		
 		mRawImage = NULL ;
@@ -1109,10 +1101,6 @@ bool LLTextureFetchWorker::doWork(S32 param)
 									 << " Bytes: " << mRequestedSize
 									 << " Bandwidth(kbps): " << mFetcher->getTextureBandwidth() << "/" << mFetcher->mMaxBandwidth
 									 << LL_ENDL;
-// 				LL_WARNS("Texture") << "HTTP GET: " << mID << " Offset: " << mRequestedOffset
-// 									<< " Bytes: " << mRequestedSize
-// 									<< " Bandwidth(kbps): " << mFetcher->getTextureBandwidth() << "/" << mFetcher->mMaxBandwidth
-// 									<< LL_ENDL;
 
 				// Will call callbackHttpGet when curl request completes
 				// *FIXME:  enable redirection follow
@@ -1241,7 +1229,7 @@ bool LLTextureFetchWorker::doWork(S32 param)
 				}
 			}
 						
-			if (mHaveAllData && mRequestedDiscard == 0) //the image file is fully loaded.
+			if (mHaveAllData /* && mRequestedDiscard == 0*/) //the image file is fully loaded.
 			{
 				mFileSize = total_size;
 			}
@@ -1692,13 +1680,32 @@ S32 LLTextureFetchWorker::callbackHttpGet(LLCore::HttpResponse * response,
 			body->addRef();
 			mHttpBufferArray = body;
 
-			if (data_size < mRequestedSize && mRequestedDiscard == 0)
+			if (! partial)
+			{
+				// Response indicates this is the entire asset regardless
+				// of our asking for a byte range.  Mark it so and drop
+				// any partial data we might have so that the current
+				// response body becomes the entire dataset.
+				if (data_size <= mRequestedOffset)
+				{
+					LL_WARNS("Texture") << "Fetched entire texture " << mID
+										<< " when it was expected to be marked complete.  mImageSize:  "
+										<< mFileSize << " datasize:  " << mFormattedImage->getDataSize()
+										<< LL_ENDL;
+				}
+				mHaveAllData = TRUE;
+				llassert_always(mDecodeHandle == 0);
+				mFormattedImage = NULL; // discard any previous data we had
+			}
+			else if (data_size < mRequestedSize && mRequestedDiscard == 0)
 			{
+				// *FIXME:  I think we can treat this as complete regardless
+				// of requested discard level.  Revisit this...
 				mHaveAllData = TRUE;
 			}
 			else if (data_size > mRequestedSize)
 			{
-				// *TODO: This shouldn't be happening any more
+				// *TODO: This shouldn't be happening any more  (REALLY don't expect this anymore)
 				llwarns << "data_size = " << data_size << " > requested: " << mRequestedSize << llendl;
 				mHaveAllData = TRUE;
 				llassert_always(mDecodeHandle == 0);
-- 
GitLab