1. simon_linden
  2. viewer-rabbit

Commits

Monty Brandenberg  committed 4692bdf

SH-3240 Capture Content-Type and Content-Encoding headers.
HttpResponse object now has two strings for these content headers.
Either or both may be empty. Tidied up the cross-platform string
code and got more defensive about the length of a header line.
Integration test for the new response object.

  • Participants
  • Parent commits 117a201
  • Branches default

Comments (0)

Files changed (5)

File indra/llcorehttp/_httpoprequest.cpp

View file
  • Ignore whitespace
 							   std::string & safe_line);
 
 
-#if LL_WINDOWS
+// OS-neutral string comparisons of various types
+int os_strncasecmp(const char *s1, const char *s2, size_t n);
+int os_strcasecmp(const char *s1, const char *s2);
+char * os_strtok_r(char *str, const char *delim, char **saveptr);
 
-// Not available on windows where the legacy strtok interface
-// is thread-safe.
-char *strtok_r(char *str, const char *delim, char **saveptr);
 
-#endif // LL_WINDOWS
+static const char * const hdr_whitespace(" \t");
+static const char * const hdr_separator(": \t");
 
-
-}
+} // end anonymous namespace
 
 
 namespace LLCore
 			// Got an explicit offset/length in response
 			response->setRange(mReplyOffset, mReplyLength, mReplyFullLength);
 		}
-
+		response->setContent(mReplyConType, mReplyConEncode);
+		
 		mUserHandler->onCompleted(static_cast<HttpHandle>(this), response);
 
 		response->release();
 								HttpOptions * options,
 								HttpHeaders * headers)
 {
-	mProcFlags = 0;
+	mProcFlags = PF_SCAN_CONTENT_HEADERS;		// Always scan for content headers
 	mReqPolicy = policy_id;
 	mReqPriority = priority;
 	mReqURL = url;
 		mReplyHeaders->release();
 		mReplyHeaders = NULL;
 	}
+	mReplyConType.clear();
+	mReplyConEncode.clear();
 	
 	// *FIXME:  better error handling later
 	HttpStatus status;
 	}
 	curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mCurlHeaders);
 
-	if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS))
+	if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS | PF_SCAN_CONTENT_HEADERS))
 	{
 		curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback);
 		curl_easy_setopt(mCurlHandle, CURLOPT_HEADERDATA, this);
 
 	static const char con_ran_line[] = "content-range:";
 	static const size_t con_ran_line_len = sizeof(con_ran_line) - 1;
+
+	static const char con_type_line[] = "content-type:";
+	static const size_t con_type_line_len = sizeof(con_type_line) - 1;
+	
+	static const char con_enc_line[] = "content-encoding:";
+	static const size_t con_enc_line_len = sizeof(con_enc_line) - 1;
 	
 	HttpOpRequest * op(static_cast<HttpOpRequest *>(userdata));
 
 	const size_t hdr_size(size * nmemb);
 	const char * hdr_data(static_cast<const char *>(data));		// Not null terminated
-	
+
 	if (hdr_size >= status_line_len && ! strncmp(status_line, hdr_data, status_line_len))
 	{
 		// One of possibly several status lines.  Reset what we know and start over
 		op->mReplyOffset = 0;
 		op->mReplyLength = 0;
 		op->mReplyFullLength = 0;
+		op->mReplyConType.clear();
+		op->mReplyConEncode.clear();
 		op->mStatus = HttpStatus();
 		if (op->mReplyHeaders)
 		{
 			op->mReplyHeaders->mHeaders.clear();
 		}
 	}
-	else if (op->mProcFlags & PF_SCAN_RANGE_HEADER)
+
+	// Nothing in here wants a final CR/LF combination.  Remove
+	// it as much as possible.
+	size_t wanted_hdr_size(hdr_size);
+	if (wanted_hdr_size && '\n' == hdr_data[wanted_hdr_size - 1])
+	{
+		if (--wanted_hdr_size && '\r' == hdr_data[wanted_hdr_size - 1])
+		{
+			--wanted_hdr_size;
+		}
+	}
+	
+	// Save header if caller wants them in the response
+	if (op->mProcFlags & PF_SAVE_HEADERS)
+	{
+		// Save headers in response
+		if (! op->mReplyHeaders)
+		{
+			op->mReplyHeaders = new HttpHeaders;
+		}
+		op->mReplyHeaders->mHeaders.push_back(std::string(hdr_data, wanted_hdr_size));
+	}
+
+	// Detect and parse 'Content-Range' headers
+	if (op->mProcFlags & PF_SCAN_RANGE_HEADER)
 	{
 		char hdr_buffer[128];			// Enough for a reasonable header
-		size_t frag_size((std::min)(hdr_size, sizeof(hdr_buffer) - 1));
+		size_t frag_size((std::min)(wanted_hdr_size, sizeof(hdr_buffer) - 1));
 		
 		memcpy(hdr_buffer, hdr_data, frag_size);
 		hdr_buffer[frag_size] = '\0';
-#if LL_WINDOWS
-		if (! _strnicmp(hdr_buffer, con_ran_line, (std::min)(frag_size, con_ran_line_len)))
-#else
-		if (! strncasecmp(hdr_buffer, con_ran_line, (std::min)(frag_size, con_ran_line_len)))
-#endif	// LL_WINDOWS
+		if (frag_size > con_ran_line_len &&
+			! os_strncasecmp(hdr_buffer, con_ran_line, con_ran_line_len))
 		{
 			unsigned int first(0), last(0), length(0);
 			int status;
 		}
 	}
 
-	if (op->mProcFlags & PF_SAVE_HEADERS)
+	// Detect and parse 'Content-Type' and 'Content-Encoding' headers
+	if (op->mProcFlags & PF_SCAN_CONTENT_HEADERS)
 	{
-		// Save headers in response
-		if (! op->mReplyHeaders)
+		if (wanted_hdr_size > con_type_line_len &&
+			! os_strncasecmp(hdr_data, con_type_line, con_type_line_len))
 		{
-			op->mReplyHeaders = new HttpHeaders;
+			// Found 'Content-Type:', extract single-token value
+			std::string rhs(hdr_data + con_type_line_len, wanted_hdr_size - con_type_line_len);
+			std::string::size_type begin(0), end(rhs.size()), pos;
+
+			if ((pos = rhs.find_first_not_of(hdr_whitespace)) != std::string::npos)
+			{
+				begin = pos;
+			}
+			if ((pos = rhs.find_first_of(hdr_whitespace, begin)) != std::string::npos)
+			{
+				end = pos;
+			}
+			op->mReplyConType.assign(rhs, begin, end - begin);
 		}
-		size_t wanted_size(hdr_size);
-		if (wanted_size && '\n' == hdr_data[wanted_size - 1])
+		else if (wanted_hdr_size > con_enc_line_len &&
+				 ! os_strncasecmp(hdr_data, con_enc_line, con_enc_line_len))
 		{
-			if (--wanted_size && '\r' == hdr_data[wanted_size - 1])
+			// Found 'Content-Encoding:', extract single-token value
+			std::string rhs(hdr_data + con_enc_line_len, wanted_hdr_size - con_enc_line_len);
+			std::string::size_type begin(0), end(rhs.size()), pos;
+
+			if ((pos = rhs.find_first_not_of(hdr_whitespace)) != std::string::npos)
 			{
-				--wanted_size;
+				begin = pos;
 			}
+			if ((pos = rhs.find_first_of(hdr_whitespace, begin)) != std::string::npos)
+			{
+				end = pos;
+			}
+			op->mReplyConEncode.assign(rhs, begin, end - begin);
 		}
-		op->mReplyHeaders->mHeaders.push_back(std::string(hdr_data, wanted_size));
 	}
 
 	return hdr_size;
 	char * tok_state(NULL), * tok(NULL);
 	bool match(true);
 			
-	if (! strtok_r(buffer, ": \t", &tok_state))
+	if (! os_strtok_r(buffer, hdr_separator, &tok_state))
 		match = false;
-	if (match && (tok = strtok_r(NULL, " \t", &tok_state)))
-#if LL_WINDOWS
-		match = 0 == _stricmp("bytes", tok);
-#else
-		match = 0 == strcasecmp("bytes", tok);
-#endif // LL_WINDOWS
-	if (match && ! (tok = strtok_r(NULL, " \t", &tok_state)))
+	if (match && (tok = os_strtok_r(NULL, hdr_whitespace, &tok_state)))
+		match = 0 == os_strcasecmp("bytes", tok);
+	if (match && ! (tok = os_strtok_r(NULL, " \t", &tok_state)))
 		match = false;
 	if (match)
 	{
 	return 1;
 }
 
-#if LL_WINDOWS
-
-char *strtok_r(char *str, const char *delim, char ** savestate)
-{
-	return strtok_s(str, delim, savestate);
-}
-
-#endif // LL_WINDOWS
-
 
 void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub, std::string & safe_line)
 {
 }
 
 
+int os_strncasecmp(const char *s1, const char *s2, size_t n)
+{
+#if LL_WINDOWS
+	return _strnicmp(s1, s2, n);
+#else
+	return strncasecmp(s1, s2, n);
+#endif	// LL_WINDOWS
+}
+
+
+int os_strcasecmp(const char *s1, const char *s2)
+{
+#if LL_WINDOWS
+	return _stricmp(s1, s2);
+#else
+	return strcasecmp(s1, s2);
+#endif // LL_WINDOWS
+}
+
+
+char * os_strtok_r(char *str, const char *delim, char ** savestate)
+{
+#if LL_WINDOWS
+	return strtok_s(str, delim, savestate);
+#else
+	return strtok_r(str, delim, savestate);
+#endif
+}
+
+
 }  // end anonymous namespace
 
 		

File indra/llcorehttp/_httpoprequest.h

View file
  • Ignore whitespace
 
 #include "linden_common.h"		// Modifies curl/curl.h interfaces
 
+#include <string>
 #include <curl/curl.h>
 
 #include "httpcommon.h"
 	unsigned int		mProcFlags;
 	static const unsigned int	PF_SCAN_RANGE_HEADER = 0x00000001U;
 	static const unsigned int	PF_SAVE_HEADERS = 0x00000002U;
+	static const unsigned int	PF_SCAN_CONTENT_HEADERS = 0x00000004U;
 
 public:
 	// Request data
 	size_t				mReplyLength;
 	size_t				mReplyFullLength;
 	HttpHeaders *		mReplyHeaders;
+	std::string			mReplyConType;
+	std::string			mReplyConEncode;
 
 	// Policy data
 	int					mPolicyRetries;

File indra/llcorehttp/httpresponse.h

View file
  • Ignore whitespace
 #define	_LLCORE_HTTP_RESPONSE_H_
 
 
+#include <string>
+
 #include "httpcommon.h"
 
 #include "_refcounted.h"
 			mReplyLength = length;
 			mReplyFullLength = full_length;
 		}
-			
+
+	///
+	void getContent(std::string & con_type, std::string & con_encode) const
+		{
+			con_type = mContentType;
+			con_encode = mContentEncoding;
+		}
+
+	void setContent(const std::string & con_type, const std::string & con_encode)
+		{
+			mContentType = con_type;
+			mContentEncoding = con_encode;
+		}
+
 protected:
 	// Response data here
 	HttpStatus			mStatus;
 	unsigned int		mReplyFullLength;
 	BufferArray *		mBufferArray;
 	HttpHeaders *		mHeaders;
+	std::string			mContentType;
+	std::string			mContentEncoding;
 };
 
 

File indra/llcorehttp/tests/test_httprequest.hpp

View file
  • Ignore whitespace
 				ensure("Special header X-LL-Special in response", found_special);
 			}
 			
+			if (! mCheckContentType.empty())
+			{
+				ensure("Response required with content type check", response != NULL);
+				std::string con_type, con_enc;
+				response->getContent(con_type, con_enc);
+				ensure("Content-Type as expected (" + mCheckContentType + ")",
+					   mCheckContentType == con_type);
+			}
+
 			// std::cout << "TestHandler2::onCompleted() invoked" << std::endl;
 		}
 
 	std::string mName;
 	HttpHandle mExpectHandle;
 	bool mCheckHeader;
+	std::string mCheckContentType;
 };
 
 typedef test_group<HttpRequestTestData> HttpRequestTestGroupType;
 	}
 }
 
+// Test retrieval of Content-Type/Content-Encoding headers
+template <> template <>
+void HttpRequestTestObjectType::test<15>()
+{
+	ScopedCurlInit ready;
+
+	std::string url_base(get_base_url());
+	// std::cerr << "Base:  "  << url_base << std::endl;
+	
+	set_test_name("HttpRequest GET with Content-Type");
+
+	// 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");
+
+	// Load and clear the string setting to preload std::string object
+	// for memory return tests.
+	handler.mCheckContentType = "application/llsd+xml";
+	handler.mCheckContentType.clear();
+		
+	// record the total amount of dynamically allocated memory
+	mMemTotal = GetMemTotal();
+	mHandlerCalls = 0;
+
+	HttpRequest * req = 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();
+		ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
+
+		// Issue a GET that *can* connect
+		mStatus = HttpStatus(200);
+		handler.mCheckContentType = "application/llsd+xml";
+		HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID,
+											0U,
+											url_base,
+											NULL,
+											NULL,
+											&handler);
+		ensure("Valid handle returned for ranged 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.mCheckContentType.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 the request object
+		delete req;
+		req = NULL;
+
+		// Shut down service
+		HttpRequest::destroyService();
+	
+		ensure("Two handler calls on the way out", 2 == mHandlerCalls);
+
+#if defined(WIN32)
+		// Can only do this memory test on Windows.  On other platforms,
+		// the LL logging system holds on to memory and produces what looks
+		// like memory leaks...
+	
+		// printf("Old mem:  %d, New mem:  %d\n", mMemTotal, GetMemTotal());
+		ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
+#endif
+	}
+	catch (...)
+	{
+		stop_thread(req);
+		delete req;
+		HttpRequest::destroyService();
+		throw;
+	}
+}
+
+
 }  // end namespace tut
 
 namespace

File indra/llcorehttp/tests/test_llcorehttp_peer.py

View file
  • Ignore whitespace
 
 $LicenseInfo:firstyear=2008&license=viewerlgpl$
 Second Life Viewer Source Code
-Copyright (C) 2010, Linden Research, Inc.
+Copyright (C) 2012, Linden Research, Inc.
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public