From 8d3e5f3959b071226c4a5c2c68d9fe8664ae1ffd Mon Sep 17 00:00:00 2001
From: Monty Brandenberg <monty@lindenlab.com>
Date: Mon, 16 Jul 2012 17:35:44 -0400
Subject: [PATCH] SH-3241 Validate header correctness, SH-3243 more unit tests
 Define expectations for headers for GET, POST, PUT requests. Document those
 in the interface, test those with integration tests. Verify that header
 overrides work as expected.

---
 indra/llcorehttp/_httpoprequest.cpp           |   4 +
 indra/llcorehttp/httprequest.h                | 175 ++--
 indra/llcorehttp/tests/test_httprequest.hpp   | 844 +++++++++++++++++-
 .../llcorehttp/tests/test_llcorehttp_peer.py  |   1 +
 4 files changed, 960 insertions(+), 64 deletions(-)

diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp
index 1a770f67be5..a18a164f0d6 100644
--- a/indra/llcorehttp/_httpoprequest.cpp
+++ b/indra/llcorehttp/_httpoprequest.cpp
@@ -468,6 +468,8 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
 			curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, static_cast<void *>(NULL));
 			curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDSIZE, data_size);
 			mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:");
+			mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
+			mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
 		}
 		break;
 		
@@ -482,6 +484,8 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
 			curl_easy_setopt(mCurlHandle, CURLOPT_INFILESIZE, data_size);
 			curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, (void *) NULL);
 			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/httprequest.h b/indra/llcorehttp/httprequest.h
index 9dd53f4483c..ab2f302d34b 100644
--- a/indra/llcorehttp/httprequest.h
+++ b/indra/llcorehttp/httprequest.h
@@ -214,14 +214,46 @@ class HttpRequest
 	/// request in other requests (like cancellation) and will be an
 	/// argument when any HttpHandler object is invoked.
 	///
+	/// Headers supplied by default:
+	/// - Connection: keep-alive
+	/// - Accept: */*
+	/// - Accept-Encoding: deflate, gzip
+	/// - Keep-alive: 300
+	/// - Host: <stuff>
+	///
+	/// Some headers excluded by default:
+	/// - Pragma:
+	/// - Cache-control:
+	/// - Range:
+	/// - Transfer-Encoding:
+	/// - Referer:
+	///
 	/// @param	policy_id		Default or user-defined policy class under
 	///							which this request is to be serviced.
 	/// @param	priority		Standard priority scheme inherited from
 	///							Indra code base (U32-type scheme).
-	/// @param	url
-	/// @param	options			(optional)
-	/// @param	headers			(optional)
-	/// @param	handler			(optional)
+	/// @param	url				URL with any encoded query parameters to
+	///							be accessed.
+	/// @param	options			Optional instance of an HttpOptions object
+	///							to provide additional controls over the request
+	///							function for this request only.  Any such
+	///							object then becomes shared-read across threads
+	///							and no code should modify the HttpOptions
+	///							instance.
+	/// @param	headers			Optional instance of an HttpHeaders object
+	///							to provide additional and/or overridden
+	///							headers for the request.  As with options,
+	///							the instance becomes shared-read across threads
+	///							and no code should modify the HttpHeaders
+	///							instance.
+	/// @param	handler			Optional pointer to an HttpHandler instance
+	///							whose onCompleted() method will be invoked
+	///							during calls to update().  This is a non-
+	///							reference-counted object which would be a
+	///							problem for shutdown and other edge cases but
+	///							the pointer is only dereferenced during
+	///							calls to update().
+	///
 	/// @return					The handle of the request if successfully
 	///							queued or LLCORE_HTTP_HANDLE_INVALID if the
 	///							request could not be queued.  In the latter
@@ -244,20 +276,29 @@ class HttpRequest
 	/// request in other requests (like cancellation) and will be an
 	/// argument when any HttpHandler object is invoked.
 	///
-	/// @param	policy_id		Default or user-defined policy class under
-	///							which this request is to be serviced.
-	/// @param	priority		Standard priority scheme inherited from
-	///							Indra code base (U32-type scheme).
-	/// @param	url
-	/// @param	offset
-	/// @param	len
-	/// @param	options			(optional)
-	/// @param	headers			(optional)
-	/// @param	handler			(optional)
-	/// @return					The handle of the request if successfully
-	///							queued or LLCORE_HTTP_HANDLE_INVALID if the
-	///							request could not be queued.  In the latter
-	///							case, @see getStatus() will return more info.
+	/// Headers supplied by default:
+	/// - Connection: keep-alive
+	/// - Accept: */*
+	/// - Accept-Encoding: deflate, gzip
+	/// - Keep-alive: 300
+	/// - Host: <stuff>
+	/// - Range: <stuff>  (will be omitted if offset == 0 and len == 0)
+	///
+	/// Some headers excluded by default:
+	/// - Pragma:
+	/// - Cache-control:
+	/// - Transfer-Encoding:
+	/// - Referer:
+	///
+	/// @param	policy_id		@see requestGet()
+	/// @param	priority		"
+	/// @param	url				"
+	/// @param	offset			Offset of first byte into resource to be returned.
+	/// @param	len				Count of bytes to be returned
+	/// @param	options			@see requestGet()
+	/// @param	headers			"
+	/// @param	handler			"
+	/// @return					"
 	///
 	HttpHandle requestGetByteRange(policy_t policy_id,
 								   priority_t priority,
@@ -269,22 +310,37 @@ class HttpRequest
 								   HttpHandler * handler);
 
 
-	///
-	/// @param	policy_id		Default or user-defined policy class under
-	///							which this request is to be serviced.
-	/// @param	priority		Standard priority scheme inherited from
-	///							Indra code base.
-	/// @param	url
+	/// Queue a full HTTP POST.  Query arguments and body may
+	/// be provided.  Caller is responsible for escaping and
+	/// encoding and communicating the content types.
+	///
+	/// Headers supplied by default:
+	/// - Connection: keep-alive
+	/// - Accept: */*
+	/// - Accept-Encoding: deflate, gzip
+	/// - Keep-Alive: 300
+	/// - Host: <stuff>
+	/// - Content-Length: <digits>
+	/// - Content-Type: application/x-www-form-urlencoded
+	///
+	/// Some headers excluded by default:
+	/// - Pragma:
+	/// - Cache-Control:
+	/// - Transfer-Encoding: ... chunked ...
+	/// - Referer:
+	/// - Content-Encoding:
+	/// - Expect:
+	///
+	/// @param	policy_id		@see requestGet()
+	/// @param	priority		"
+	/// @param	url				"
 	/// @param	body			Byte stream to be sent as the body.  No
 	///							further encoding or escaping will be done
 	///							to the content.
-	/// @param	options			(optional)
-	/// @param	headers			(optional)
-	/// @param	handler			(optional)
-	/// @return					The handle of the request if successfully
-	///							queued or LLCORE_HTTP_HANDLE_INVALID if the
-	///							request could not be queued.  In the latter
-	///							case, @see getStatus() will return more info.
+	/// @param	options			@see requestGet()K(optional)
+	/// @param	headers			"
+	/// @param	handler			"
+	/// @return					"
 	///
 	HttpHandle requestPost(policy_t policy_id,
 						   priority_t priority,
@@ -295,22 +351,37 @@ class HttpRequest
 						   HttpHandler * handler);
 
 
-	///
-	/// @param	policy_id		Default or user-defined policy class under
-	///							which this request is to be serviced.
-	/// @param	priority		Standard priority scheme inherited from
-	///							Indra code base.
-	/// @param	url
+	/// Queue a full HTTP PUT.  Query arguments and body may
+	/// be provided.  Caller is responsible for escaping and
+	/// encoding and communicating the content types.
+	///
+	/// Headers supplied by default:
+	/// - Connection: keep-alive
+	/// - Accept: */*
+	/// - Accept-Encoding: deflate, gzip
+	/// - Keep-Alive: 300
+	/// - Host: <stuff>
+	/// - Content-Length: <digits>
+	///
+	/// Some headers excluded by default:
+	/// - Pragma:
+	/// - Cache-Control:
+	/// - Transfer-Encoding: ... chunked ...
+	/// - Referer:
+	/// - Content-Encoding:
+	/// - Expect:
+	/// - Content-Type:
+	///
+	/// @param	policy_id		@see requestGet()
+	/// @param	priority		"
+	/// @param	url				"
 	/// @param	body			Byte stream to be sent as the body.  No
 	///							further encoding or escaping will be done
 	///							to the content.
-	/// @param	options			(optional)
-	/// @param	headers			(optional)
-	/// @param	handler			(optional)
-	/// @return					The handle of the request if successfully
-	///							queued or LLCORE_HTTP_HANDLE_INVALID if the
-	///							request could not be queued.  In the latter
-	///							case, @see getStatus() will return more info.
+	/// @param	options			@see requestGet()K(optional)
+	/// @param	headers			"
+	/// @param	handler			"
+	/// @return					"
 	///
 	HttpHandle requestPut(policy_t policy_id,
 						  priority_t priority,
@@ -326,11 +397,8 @@ class HttpRequest
 	/// immediately processes it and returns the request to the reply
 	/// queue.
 	///
-	/// @param	handler			(optional)
-	/// @return					The handle of the request if successfully
-	///							queued or LLCORE_HTTP_HANDLE_INVALID if the
-	///							request could not be queued.  In the latter
-	///							case, @see getStatus() will return more info.
+	/// @param	handler			@see requestGet()
+	/// @return					"
 	///
 	HttpHandle requestNoOp(HttpHandler * handler);
 
@@ -345,7 +413,8 @@ class HttpRequest
 	///							spend in the call.  As hinted at above, this
 	///							is partly a function of application code so it's
 	///							a soft limit.  A '0' value will run without
-	///							time limit.
+	///							time limit until everything queued has been
+	///							delivered.
 	///
 	/// @return					Standard status code.
 	HttpStatus update(long usecs);
@@ -365,10 +434,8 @@ class HttpRequest
 	/// @param	request			Handle of previously-issued request to
 	///							be changed.
 	/// @param	priority		New priority value.
-	/// @param	handler			(optional)
-	/// @return					The handle of the request if successfully
-	///							queued or LLCORE_HTTP_HANDLE_INVALID if the
-	///							request could not be queued.
+	/// @param	handler			@see requestGet()
+	/// @return					"
 	///
 	HttpHandle requestSetPriority(HttpHandle request, priority_t priority, HttpHandler * handler);
 
diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp
index 1acf4f9d4b1..ec144693c36 100644
--- a/indra/llcorehttp/tests/test_httprequest.hpp
+++ b/indra/llcorehttp/tests/test_httprequest.hpp
@@ -1705,11 +1705,18 @@ void HttpRequestTestObjectType::test<16>()
 		
 		// Issue a GET that *can* connect
 		mStatus = HttpStatus(200);
-		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\W*keep-alive", boost::regex::icase));
-		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\W*\\*/\\*", boost::regex::icase));
-		handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-cache-control:.*", boost::regex::icase));
-		handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-pragma:.*", boost::regex::icase));
-		handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-range:.*", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-type:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase));
 		HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID,
 											0U,
 											url_base + "reflect/",
@@ -1735,12 +1742,19 @@ void HttpRequestTestObjectType::test<16>()
 		
 		mStatus = HttpStatus(200);
 		handler.mHeadersRequired.clear();
-		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\W*keep-alive", boost::regex::icase));
-		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\W*\\image/x-j2c", boost::regex::icase));
-		handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-range:.*", boost::regex::icase));
 		handler.mHeadersDisallowed.clear();
-		handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-cache-control:.*", boost::regex::icase));
-		handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-pragma:.*", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*image/x-j2c", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("\\W*X-Reflect-range:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-type:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase));
 		handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
 										  0U,
 										  url_base + "reflect/",
@@ -1830,6 +1844,816 @@ void HttpRequestTestObjectType::test<16>()
 }
 
 
+// Test header generation on POST requests
+template <> template <>
+void HttpRequestTestObjectType::test<17>()
+{
+	ScopedCurlInit ready;
+
+	// Warmup boost::regex to pre-alloc memory for memory size tests
+	boost::regex warmup("askldjflasdj;f", boost::regex::icase);
+	boost::regex_match("akl;sjflajfk;ajsk", warmup);
+
+	std::string url_base(get_base_url());
+	
+	set_test_name("Header generation for HttpRequest POST");
+
+	// Handler can be stack-allocated *if* there are no dangling
+	// references to it after completion of this method.
+	// Create before memory record as the string copy will bump numbers.
+	TestHandler2 handler(this, "handler");
+
+	// record the total amount of dynamically allocated memory
+	mMemTotal = GetMemTotal();
+	mHandlerCalls = 0;
+
+	HttpRequest * req = NULL;
+	HttpOptions * options = NULL;
+	HttpHeaders * headers = NULL;
+	BufferArray * ba = NULL;
+	
+	try
+	{
+		// Get singletons created
+		HttpRequest::createService();
+		
+		// Start threading early so that thread memory is invariant
+		// over the test.
+		HttpRequest::startThread();
+
+		// create a new ref counted object with an implicit reference
+		req = new HttpRequest();
+
+		// options set
+		options = new HttpOptions();
+		options->setWantHeaders(true);
+
+		// And a buffer array
+		const char * msg("It was the best of times, it was the worst of times.");
+		ba = new BufferArray;
+		ba->append(msg, strlen(msg));
+			
+		// Issue a default POST
+		mStatus = HttpStatus(200);
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-type:\\s*application/x-www-form-urlencoded", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase));
+		HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID,
+											 0U,
+											 url_base + "reflect/",
+											 ba,
+											 options,
+											 NULL,
+											 &handler);
+		ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID);
+		ba->release();
+		ba = NULL;
+			
+		// Run the notification pump.
+		int count(0);
+		int limit(10);
+		while (count++ < limit && mHandlerCalls < 1)
+		{
+			req->update(1000000);
+			usleep(100000);
+		}
+		ensure("Request executed in reasonable time", count < limit);
+		ensure("One handler invocation for request", mHandlerCalls == 1);
+
+
+		// Okay, request a shutdown of the servicing thread
+		mStatus = HttpStatus();
+		handler.mHeadersRequired.clear();
+		handler.mHeadersDisallowed.clear();
+		handle = req->requestStopThread(&handler);
+		ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID);
+	
+		// Run the notification pump again
+		count = 0;
+		limit = 10;
+		while (count++ < limit && mHandlerCalls < 2)
+		{
+			req->update(1000000);
+			usleep(100000);
+		}
+		ensure("Second request executed in reasonable time", count < limit);
+		ensure("Second handler invocation", mHandlerCalls == 2);
+
+		// See that we actually shutdown the thread
+		count = 0;
+		limit = 10;
+		while (count++ < limit && ! HttpService::isStopped())
+		{
+			usleep(100000);
+		}
+		ensure("Thread actually stopped running", HttpService::isStopped());
+	
+		// release options & headers
+		if (options)
+		{
+			options->release();
+		}
+		options = NULL;
+
+		if (headers)
+		{
+			headers->release();
+		}
+		headers = NULL;
+		
+		// release the request object
+		delete req;
+		req = NULL;
+
+		// Shut down service
+		HttpRequest::destroyService();
+	}
+	catch (...)
+	{
+		stop_thread(req);
+		if (ba)
+		{
+			ba->release();
+			ba = NULL;
+		}
+		if (options)
+		{
+			options->release();
+			options = NULL;
+		}
+		if (headers)
+		{
+			headers->release();
+			headers = NULL;
+		}
+		delete req;
+		HttpRequest::destroyService();
+		throw;
+	}
+}
+
+
+// Test header generation on PUT requests
+template <> template <>
+void HttpRequestTestObjectType::test<18>()
+{
+	ScopedCurlInit ready;
+
+	// Warmup boost::regex to pre-alloc memory for memory size tests
+	boost::regex warmup("askldjflasdj;f", boost::regex::icase);
+	boost::regex_match("akl;sjflajfk;ajsk", warmup);
+
+	std::string url_base(get_base_url());
+	
+	set_test_name("Header generation for HttpRequest PUT");
+
+	// Handler can be stack-allocated *if* there are no dangling
+	// references to it after completion of this method.
+	// Create before memory record as the string copy will bump numbers.
+	TestHandler2 handler(this, "handler");
+
+	// record the total amount of dynamically allocated memory
+	mMemTotal = GetMemTotal();
+	mHandlerCalls = 0;
+
+	HttpRequest * req = NULL;
+	HttpOptions * options = NULL;
+	HttpHeaders * headers = NULL;
+	BufferArray * ba = NULL;
+	
+	try
+	{
+		// Get singletons created
+		HttpRequest::createService();
+		
+		// Start threading early so that thread memory is invariant
+		// over the test.
+		HttpRequest::startThread();
+
+		// create a new ref counted object with an implicit reference
+		req = new HttpRequest();
+
+		// options set
+		options = new HttpOptions();
+		options->setWantHeaders(true);
+
+		// And a buffer array
+		const char * msg("It was the best of times, it was the worst of times.");
+		ba = new BufferArray;
+		ba->append(msg, strlen(msg));
+			
+		// Issue a default PUT
+		mStatus = HttpStatus(200);
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:.*", boost::regex::icase));
+		HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID,
+											0U,
+											url_base + "reflect/",
+											ba,
+											options,
+											NULL,
+											&handler);
+		ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID);
+		ba->release();
+		ba = NULL;
+			
+		// Run the notification pump.
+		int count(0);
+		int limit(10);
+		while (count++ < limit && mHandlerCalls < 1)
+		{
+			req->update(1000000);
+			usleep(100000);
+		}
+		ensure("Request executed in reasonable time", count < limit);
+		ensure("One handler invocation for request", mHandlerCalls == 1);
+
+
+		// Okay, request a shutdown of the servicing thread
+		mStatus = HttpStatus();
+		handler.mHeadersRequired.clear();
+		handler.mHeadersDisallowed.clear();
+		handle = req->requestStopThread(&handler);
+		ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID);
+	
+		// Run the notification pump again
+		count = 0;
+		limit = 10;
+		while (count++ < limit && mHandlerCalls < 2)
+		{
+			req->update(1000000);
+			usleep(100000);
+		}
+		ensure("Second request executed in reasonable time", count < limit);
+		ensure("Second handler invocation", mHandlerCalls == 2);
+
+		// See that we actually shutdown the thread
+		count = 0;
+		limit = 10;
+		while (count++ < limit && ! HttpService::isStopped())
+		{
+			usleep(100000);
+		}
+		ensure("Thread actually stopped running", HttpService::isStopped());
+	
+		// release options & headers
+		if (options)
+		{
+			options->release();
+		}
+		options = NULL;
+
+		if (headers)
+		{
+			headers->release();
+		}
+		headers = NULL;
+		
+		// release the request object
+		delete req;
+		req = NULL;
+
+		// Shut down service
+		HttpRequest::destroyService();
+	}
+	catch (...)
+	{
+		stop_thread(req);
+		if (ba)
+		{
+			ba->release();
+			ba = NULL;
+		}
+		if (options)
+		{
+			options->release();
+			options = NULL;
+		}
+		if (headers)
+		{
+			headers->release();
+			headers = NULL;
+		}
+		delete req;
+		HttpRequest::destroyService();
+		throw;
+	}
+}
+
+
+// Test header generation on GET requests with overrides
+template <> template <>
+void HttpRequestTestObjectType::test<19>()
+{
+	ScopedCurlInit ready;
+
+	// Warmup boost::regex to pre-alloc memory for memory size tests
+	boost::regex warmup("askldjflasdj;f", boost::regex::icase);
+	boost::regex_match("akl;sjflajfk;ajsk", warmup);
+
+	std::string url_base(get_base_url());
+	
+	set_test_name("Header generation for HttpRequest GET with header overrides");
+
+	// Handler can be stack-allocated *if* there are no dangling
+	// references to it after completion of this method.
+	// Create before memory record as the string copy will bump numbers.
+	TestHandler2 handler(this, "handler");
+
+	// record the total amount of dynamically allocated memory
+	mMemTotal = GetMemTotal();
+	mHandlerCalls = 0;
+
+	HttpRequest * req = NULL;
+	HttpOptions * options = NULL;
+	HttpHeaders * headers = NULL;
+
+	try
+	{
+		// Get singletons created
+		HttpRequest::createService();
+		
+		// Start threading early so that thread memory is invariant
+		// over the test.
+		HttpRequest::startThread();
+
+		// create a new ref counted object with an implicit reference
+		req = new HttpRequest();
+
+		// options set
+		options = new HttpOptions();
+		options->setWantHeaders(true);
+
+		// headers
+		headers = new HttpHeaders;
+		headers->mHeaders.push_back("Keep-Alive: 120");
+		headers->mHeaders.push_back("Accept-encoding: deflate");
+		headers->mHeaders.push_back("Accept: text/plain");
+
+		// Issue a GET with modified headers
+		mStatus = HttpStatus(200);
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*text/plain", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*deflate", boost::regex::icase)); // close enough
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*120", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough
+		handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-keep-alive:\\s*300", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-type:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase));
+		HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID,
+											0U,
+											url_base + "reflect/",
+											options,
+											headers,
+											&handler);
+		ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID);
+
+		// Run the notification pump.
+		int count(0);
+		int limit(10);
+		while (count++ < limit && mHandlerCalls < 1)
+		{
+			req->update(1000000);
+			usleep(100000);
+		}
+		ensure("Request executed in reasonable time", count < limit);
+		ensure("One handler invocation for request", mHandlerCalls == 1);
+
+		// Okay, request a shutdown of the servicing thread
+		mStatus = HttpStatus();
+		handler.mHeadersRequired.clear();
+		handler.mHeadersDisallowed.clear();
+		handle = req->requestStopThread(&handler);
+		ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID);
+	
+		// Run the notification pump again
+		count = 0;
+		limit = 10;
+		while (count++ < limit && mHandlerCalls < 2)
+		{
+			req->update(1000000);
+			usleep(100000);
+		}
+		ensure("Second request executed in reasonable time", count < limit);
+		ensure("Second handler invocation", mHandlerCalls == 2);
+
+		// See that we actually shutdown the thread
+		count = 0;
+		limit = 10;
+		while (count++ < limit && ! HttpService::isStopped())
+		{
+			usleep(100000);
+		}
+		ensure("Thread actually stopped running", HttpService::isStopped());
+	
+		// release options & headers
+		if (options)
+		{
+			options->release();
+		}
+		options = NULL;
+
+		if (headers)
+		{
+			headers->release();
+		}
+		headers = NULL;
+		
+		// release the request object
+		delete req;
+		req = NULL;
+
+		// Shut down service
+		HttpRequest::destroyService();
+	}
+	catch (...)
+	{
+		stop_thread(req);
+		if (options)
+		{
+			options->release();
+			options = NULL;
+		}
+		if (headers)
+		{
+			headers->release();
+			headers = NULL;
+		}
+		delete req;
+		HttpRequest::destroyService();
+		throw;
+	}
+}
+
+
+// Test header generation on POST requests with overrides
+template <> template <>
+void HttpRequestTestObjectType::test<20>()
+{
+	ScopedCurlInit ready;
+
+	// Warmup boost::regex to pre-alloc memory for memory size tests
+	boost::regex warmup("askldjflasdj;f", boost::regex::icase);
+	boost::regex_match("akl;sjflajfk;ajsk", warmup);
+
+	std::string url_base(get_base_url());
+	
+	set_test_name("Header generation for HttpRequest POST with header overrides");
+
+	// Handler can be stack-allocated *if* there are no dangling
+	// references to it after completion of this method.
+	// Create before memory record as the string copy will bump numbers.
+	TestHandler2 handler(this, "handler");
+
+	// record the total amount of dynamically allocated memory
+	mMemTotal = GetMemTotal();
+	mHandlerCalls = 0;
+
+	HttpRequest * req = NULL;
+	HttpOptions * options = NULL;
+	HttpHeaders * headers = NULL;
+	BufferArray * ba = NULL;
+	
+	try
+	{
+		// Get singletons created
+		HttpRequest::createService();
+		
+		// Start threading early so that thread memory is invariant
+		// over the test.
+		HttpRequest::startThread();
+
+		// create a new ref counted object with an implicit reference
+		req = new HttpRequest();
+
+		// options set
+		options = new HttpOptions();
+		options->setWantHeaders(true);
+
+		// headers
+		headers = new HttpHeaders();
+		headers->mHeaders.push_back("keep-Alive: 120");
+		headers->mHeaders.push_back("Accept:  text/html");
+		headers->mHeaders.push_back("content-type:  application/llsd+xml");
+		headers->mHeaders.push_back("cache-control: no-store");
+		
+		// And a buffer array
+		const char * msg("<xml><llsd><string>It was the best of times, it was the worst of times.</string></llsd></xml>");
+		ba = new BufferArray;
+		ba->append(msg, strlen(msg));
+			
+		// Issue a default POST
+		mStatus = HttpStatus(200);
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*text/html", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*120", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-type:\\s*application/llsd\\+xml", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("\\s*X-Reflect-cache-control:\\s*no-store", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:\\s*application/x-www-form-urlencoded", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-keep-alive:\\s*300", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase));
+		HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID,
+											 0U,
+											 url_base + "reflect/",
+											 ba,
+											 options,
+											 headers,
+											 &handler);
+		ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID);
+		ba->release();
+		ba = NULL;
+			
+		// Run the notification pump.
+		int count(0);
+		int limit(10);
+		while (count++ < limit && mHandlerCalls < 1)
+		{
+			req->update(1000000);
+			usleep(100000);
+		}
+		ensure("Request executed in reasonable time", count < limit);
+		ensure("One handler invocation for request", mHandlerCalls == 1);
+
+
+		// Okay, request a shutdown of the servicing thread
+		mStatus = HttpStatus();
+		handler.mHeadersRequired.clear();
+		handler.mHeadersDisallowed.clear();
+		handle = req->requestStopThread(&handler);
+		ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID);
+	
+		// Run the notification pump again
+		count = 0;
+		limit = 10;
+		while (count++ < limit && mHandlerCalls < 2)
+		{
+			req->update(1000000);
+			usleep(100000);
+		}
+		ensure("Second request executed in reasonable time", count < limit);
+		ensure("Second handler invocation", mHandlerCalls == 2);
+
+		// See that we actually shutdown the thread
+		count = 0;
+		limit = 10;
+		while (count++ < limit && ! HttpService::isStopped())
+		{
+			usleep(100000);
+		}
+		ensure("Thread actually stopped running", HttpService::isStopped());
+	
+		// release options & headers
+		if (options)
+		{
+			options->release();
+		}
+		options = NULL;
+
+		if (headers)
+		{
+			headers->release();
+		}
+		headers = NULL;
+		
+		// release the request object
+		delete req;
+		req = NULL;
+
+		// Shut down service
+		HttpRequest::destroyService();
+	}
+	catch (...)
+	{
+		stop_thread(req);
+		if (ba)
+		{
+			ba->release();
+			ba = NULL;
+		}
+		if (options)
+		{
+			options->release();
+			options = NULL;
+		}
+		if (headers)
+		{
+			headers->release();
+			headers = NULL;
+		}
+		delete req;
+		HttpRequest::destroyService();
+		throw;
+	}
+}
+
+
+// Test header generation on PUT requests with overrides
+template <> template <>
+void HttpRequestTestObjectType::test<21>()
+{
+	ScopedCurlInit ready;
+
+	// Warmup boost::regex to pre-alloc memory for memory size tests
+	boost::regex warmup("askldjflasdj;f", boost::regex::icase);
+	boost::regex_match("akl;sjflajfk;ajsk", warmup);
+
+	std::string url_base(get_base_url());
+	
+	set_test_name("Header generation for HttpRequest PUT with header overrides");
+
+	// Handler can be stack-allocated *if* there are no dangling
+	// references to it after completion of this method.
+	// Create before memory record as the string copy will bump numbers.
+	TestHandler2 handler(this, "handler");
+
+	// record the total amount of dynamically allocated memory
+	mMemTotal = GetMemTotal();
+	mHandlerCalls = 0;
+
+	HttpRequest * req = NULL;
+	HttpOptions * options = NULL;
+	HttpHeaders * headers = NULL;
+	BufferArray * ba = NULL;
+	
+	try
+	{
+		// Get singletons created
+		HttpRequest::createService();
+		
+		// Start threading early so that thread memory is invariant
+		// over the test.
+		HttpRequest::startThread();
+
+		// create a new ref counted object with an implicit reference
+		req = new HttpRequest();
+
+		// options set
+		options = new HttpOptions();
+		options->setWantHeaders(true);
+
+		// headers
+		headers = new HttpHeaders;
+		headers->mHeaders.push_back("content-type:  text/plain");
+		headers->mHeaders.push_back("content-type:  text/html");
+		headers->mHeaders.push_back("content-type:  application/llsd+xml");
+		
+		// And a buffer array
+		const char * msg("<xml><llsd><string>It was the best of times, it was the worst of times.</string></llsd></xml>");
+		ba = new BufferArray;
+		ba->append(msg, strlen(msg));
+			
+		// Issue a default PUT
+		mStatus = HttpStatus(200);
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase));
+		handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-type:\\s*application/llsd\\+xml", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:\\s*text/plain", boost::regex::icase));
+		handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:\\s*text/html", boost::regex::icase));
+		HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID,
+											0U,
+											url_base + "reflect/",
+											ba,
+											options,
+											headers,
+											&handler);
+		ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID);
+		ba->release();
+		ba = NULL;
+			
+		// Run the notification pump.
+		int count(0);
+		int limit(10);
+		while (count++ < limit && mHandlerCalls < 1)
+		{
+			req->update(1000000);
+			usleep(100000);
+		}
+		ensure("Request executed in reasonable time", count < limit);
+		ensure("One handler invocation for request", mHandlerCalls == 1);
+
+
+		// Okay, request a shutdown of the servicing thread
+		mStatus = HttpStatus();
+		handler.mHeadersRequired.clear();
+		handler.mHeadersDisallowed.clear();
+		handle = req->requestStopThread(&handler);
+		ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID);
+	
+		// Run the notification pump again
+		count = 0;
+		limit = 10;
+		while (count++ < limit && mHandlerCalls < 2)
+		{
+			req->update(1000000);
+			usleep(100000);
+		}
+		ensure("Second request executed in reasonable time", count < limit);
+		ensure("Second handler invocation", mHandlerCalls == 2);
+
+		// See that we actually shutdown the thread
+		count = 0;
+		limit = 10;
+		while (count++ < limit && ! HttpService::isStopped())
+		{
+			usleep(100000);
+		}
+		ensure("Thread actually stopped running", HttpService::isStopped());
+	
+		// release options & headers
+		if (options)
+		{
+			options->release();
+		}
+		options = NULL;
+
+		if (headers)
+		{
+			headers->release();
+		}
+		headers = NULL;
+		
+		// release the request object
+		delete req;
+		req = NULL;
+
+		// Shut down service
+		HttpRequest::destroyService();
+	}
+	catch (...)
+	{
+		stop_thread(req);
+		if (ba)
+		{
+			ba->release();
+			ba = NULL;
+		}
+		if (options)
+		{
+			options->release();
+			options = NULL;
+		}
+		if (headers)
+		{
+			headers->release();
+			headers = NULL;
+		}
+		delete req;
+		HttpRequest::destroyService();
+		throw;
+	}
+}
+
+
 }  // end namespace tut
 
 namespace
diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py
index c527ce6ce0a..75a3c39ef2f 100644
--- a/indra/llcorehttp/tests/test_llcorehttp_peer.py
+++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py
@@ -141,6 +141,7 @@ def answer(self, data, withdata=True):
 
     def reflect_headers(self):
         for name in self.headers.keys():
+            # print "Header:  %s: %s" % (name, self.headers[name])
             self.send_header("X-Reflect-" + name, self.headers[name])
 
     if not VERBOSE:
-- 
GitLab