/**
 * @file _httpoprequest.cpp
 * @brief Definitions for internal class HttpOpRequest
 *
 * $LicenseInfo:firstyear=2012&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2012-2013, 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
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "_httpoprequest.h"

#include <cstdio>
#include <algorithm>

#include "httpcommon.h"
#include "httphandler.h"
#include "httpresponse.h"
#include "bufferarray.h"
#include "httpheaders.h"
#include "httpoptions.h"

#include "_httprequestqueue.h"
#include "_httpreplyqueue.h"
#include "_httpservice.h"
#include "_httppolicy.h"
#include "_httppolicyglobal.h"
#include "_httplibcurl.h"
#include "_httpinternal.h"

#include "llhttpstatuscodes.h"
#include "llproxy.h"

namespace
{

// Attempts to parse a 'Content-Range:' header.  Caller must already
// have verified that the header tag is present.  The 'buffer' argument
// will be processed by strtok_r calls which will modify the buffer.
//
// @return		-1 if invalid and response should be dropped, 0 if valid an
//				correct, 1 if couldn't be parsed.  If 0, the first, last,
//				and length arguments are also written.  'length' may be
//				0 if the length wasn't available to the server.
//
int parse_content_range_header(char * buffer,
							   unsigned int * first,
							   unsigned int * last,
							   unsigned int * length);


// Take data from libcurl's CURLOPT_DEBUGFUNCTION callback and
// escape and format it for a tracing line in logging.  Absolutely
// anything including NULs can be in the data.  If @scrub is true,
// non-printing or non-ascii characters are replaced with spaces
// otherwise a %XX form of escaping is used.
void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub,
							   std::string & safe_line);


// 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);


static const char * const hdr_whitespace(" \t");
static const char * const hdr_separator(": \t");

} // end anonymous namespace


namespace LLCore
{


HttpOpRequest::HttpOpRequest()
	: HttpOperation(),
	  mProcFlags(0U),
	  mReqMethod(HOR_GET),
	  mReqBody(NULL),
	  mReqOffset(0),
	  mReqLength(0),
	  mReqHeaders(NULL),
	  mReqOptions(NULL),
	  mCurlActive(false),
	  mCurlHandle(NULL),
	  mCurlService(NULL),
	  mCurlHeaders(NULL),
	  mCurlBodyPos(0),
	  mReplyBody(NULL),
	  mReplyOffset(0),
	  mReplyLength(0),
	  mReplyFullLength(0),
	  mReplyHeaders(NULL),
	  mPolicyRetries(0),
	  mPolicyRetryAt(HttpTime(0)),
	  mPolicyRetryLimit(HTTP_RETRY_COUNT_DEFAULT)
{
	// *NOTE:  As members are added, retry initialization/cleanup
	// may need to be extended in @see prepareRequest().
}



HttpOpRequest::~HttpOpRequest()
{
	if (mReqBody)
	{
		mReqBody->release();
		mReqBody = NULL;
	}
	
	if (mReqOptions)
	{
		mReqOptions->release();
		mReqOptions = NULL;
	}

	if (mReqHeaders)
	{
		mReqHeaders->release();
		mReqHeaders = NULL;
	}

	if (mCurlHandle)
	{
		curl_easy_cleanup(mCurlHandle);
		mCurlHandle = NULL;
	}

	mCurlService = NULL;

	if (mCurlHeaders)
	{
		curl_slist_free_all(mCurlHeaders);
		mCurlHeaders = NULL;
	}

	if (mReplyBody)
	{
		mReplyBody->release();
		mReplyBody = NULL;
	}

	if (mReplyHeaders)
	{
		mReplyHeaders->release();
		mReplyHeaders = NULL;
	}
}


void HttpOpRequest::stageFromRequest(HttpService * service)
{
	addRef();
	service->getPolicy().addOp(this);			// transfers refcount
}


void HttpOpRequest::stageFromReady(HttpService * service)
{
	addRef();
	service->getTransport().addOp(this);		// transfers refcount
}


void HttpOpRequest::stageFromActive(HttpService * service)
{
	if (mReplyLength)
	{
		// If non-zero, we received and processed a Content-Range
		// header with the response.  If there is received data
		// (and there may not be due to protocol violations,
		// HEAD requests, etc., see BUG-2295) Verify that what it
		// says is consistent with the received data.
		if (mReplyBody && mReplyBody->size() && mReplyLength != mReplyBody->size())
		{
			// Not as expected, fail the request
			mStatus = HttpStatus(HttpStatus::LLCORE, HE_INV_CONTENT_RANGE_HDR);
		}
	}
	
	if (mCurlHeaders)
	{
		// We take these headers out of the request now as they were
		// allocated originally in this thread and the notifier doesn't
		// need them.  This eliminates one source of heap moving across
		// threads.

		curl_slist_free_all(mCurlHeaders);
		mCurlHeaders = NULL;
	}

	addAsReply();
}


void HttpOpRequest::visitNotifier(HttpRequest * request)
{
	if (mUserHandler)
	{
		HttpResponse * response = new HttpResponse();
		response->setStatus(mStatus);
		response->setBody(mReplyBody);
		response->setHeaders(mReplyHeaders);
		if (mReplyOffset || mReplyLength)
		{
			// Got an explicit offset/length in response
			response->setRange(mReplyOffset, mReplyLength, mReplyFullLength);
		}
		response->setContentType(mReplyConType);
		
		mUserHandler->onCompleted(static_cast<HttpHandle>(this), response);

		response->release();
	}
}


HttpStatus HttpOpRequest::cancel()
{
	mStatus = HttpStatus(HttpStatus::LLCORE, HE_OP_CANCELED);

	addAsReply();

	return HttpStatus();
}


HttpStatus HttpOpRequest::setupGet(HttpRequest::policy_t policy_id,
								   HttpRequest::priority_t priority,
								   const std::string & url,
								   HttpOptions * options,
								   HttpHeaders * headers)
{
	setupCommon(policy_id, priority, url, NULL, options, headers);
	mReqMethod = HOR_GET;
	
	return HttpStatus();
}


HttpStatus HttpOpRequest::setupGetByteRange(HttpRequest::policy_t policy_id,
											HttpRequest::priority_t priority,
											const std::string & url,
											size_t offset,
											size_t len,
											HttpOptions * options,
											HttpHeaders * headers)
{
	setupCommon(policy_id, priority, url, NULL, options, headers);
	mReqMethod = HOR_GET;
	mReqOffset = offset;
	mReqLength = len;
	if (offset || len)
	{
		mProcFlags |= PF_SCAN_RANGE_HEADER;
	}
	
	return HttpStatus();
}


HttpStatus HttpOpRequest::setupPost(HttpRequest::policy_t policy_id,
									HttpRequest::priority_t priority,
									const std::string & url,
									BufferArray * body,
									HttpOptions * options,
									HttpHeaders * headers)
{
	setupCommon(policy_id, priority, url, body, options, headers);
	mReqMethod = HOR_POST;
	
	return HttpStatus();
}


HttpStatus HttpOpRequest::setupPut(HttpRequest::policy_t policy_id,
								   HttpRequest::priority_t priority,
								   const std::string & url,
								   BufferArray * body,
								   HttpOptions * options,
								   HttpHeaders * headers)
{
	setupCommon(policy_id, priority, url, body, options, headers);
	mReqMethod = HOR_PUT;
	
	return HttpStatus();
}


void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id,
								HttpRequest::priority_t priority,
								const std::string & url,
								BufferArray * body,
								HttpOptions * options,
								HttpHeaders * headers)
{
	mProcFlags = 0U;
	mReqPolicy = policy_id;
	mReqPriority = priority;
	mReqURL = url;
	if (body)
	{
		body->addRef();
		mReqBody = body;
	}
	if (headers && ! mReqHeaders)
	{
		headers->addRef();
		mReqHeaders = headers;
	}
	if (options && ! mReqOptions)
	{
		options->addRef();
		mReqOptions = options;
		if (options->getWantHeaders())
		{
			mProcFlags |= PF_SAVE_HEADERS;
		}
		mPolicyRetryLimit = options->getRetries();
		mPolicyRetryLimit = llclamp(mPolicyRetryLimit, HTTP_RETRY_COUNT_MIN, HTTP_RETRY_COUNT_MAX);
		mTracing = (std::max)(mTracing, llclamp(options->getTrace(), HTTP_TRACE_MIN, HTTP_TRACE_MAX));
	}
}

// Sets all libcurl options and data for a request.
//
// Used both for initial requests and to 'reload' for
// a retry, generally with a different CURL handle.
// Junk may be left around from a failed request and that
// needs to be cleaned out.
//
HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
{
	// Scrub transport and result data for retried op case
	mCurlActive = false;
	mCurlHandle = NULL;
	mCurlService = NULL;
	if (mCurlHeaders)
	{
		curl_slist_free_all(mCurlHeaders);
		mCurlHeaders = NULL;
	}
	mCurlBodyPos = 0;

	if (mReplyBody)
	{
		mReplyBody->release();
		mReplyBody = NULL;
	}
	mReplyOffset = 0;
	mReplyLength = 0;
	mReplyFullLength = 0;
	if (mReplyHeaders)
	{
		mReplyHeaders->release();
		mReplyHeaders = NULL;
	}
	mReplyConType.clear();
	
	// *FIXME:  better error handling later
	HttpStatus status;

	// Get policy options
	HttpPolicyGlobal & policy(service->getPolicy().getGlobalOptions());
	
	mCurlHandle = LLCurl::createStandardCurlHandle();

	curl_easy_setopt(mCurlHandle, CURLOPT_WRITEFUNCTION, writeCallback);
	curl_easy_setopt(mCurlHandle, CURLOPT_READFUNCTION,  readCallback);	
	curl_easy_setopt(mCurlHandle, CURLOPT_READDATA, this);
	curl_easy_setopt(mCurlHandle, CURLOPT_WRITEDATA, this);
	curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str());
	curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this);
	curl_easy_setopt(mCurlHandle, CURLOPT_MAXREDIRS, HTTP_REDIRECTS_DEFAULT);	

	const std::string * opt_value(NULL);
	long opt_long(0L);
	policy.get(HttpRequest::GP_LLPROXY, &opt_long);
	if (opt_long)
	{
		// Use the viewer-based thread-safe API which has a
		// fast/safe check for proxy enable.  Would like to
		// encapsulate this someway...
		LLProxy::getInstance()->applyProxySettings(mCurlHandle);
	}
	else if (policy.get(HttpRequest::GP_HTTP_PROXY, &opt_value))
	{
		// *TODO:  This is fine for now but get fuller socks5/
		// authentication thing going later....
		curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, opt_value->c_str());
		curl_easy_setopt(mCurlHandle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
	}
	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());
	}
	
	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)
			{
				data_size = mReqBody->size();
			}
			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;
		
	case HOR_PUT:
		{
			curl_easy_setopt(mCurlHandle, CURLOPT_UPLOAD, 1);
			long data_size(0);
			if (mReqBody)
			{
				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, "Expect:");
			mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
			mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
		}
		break;
		
	default:
		LL_ERRS("CoreHttp") << "Invalid HTTP method in request:  "
							<< int(mReqMethod)  << ".  Can't recover."
							<< LL_ENDL;
		break;
	}

	// Tracing
	if (mTracing >= HTTP_TRACE_CURL_HEADERS)
	{
		curl_easy_setopt(mCurlHandle, CURLOPT_VERBOSE, 1);
		curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGDATA, this);
		curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGFUNCTION, debugCallback);
	}
	
	// There's a CURLOPT for this now...
	if ((mReqOffset || mReqLength) && HOR_GET == mReqMethod)
	{
		static const char * const fmt1("Range: bytes=%lu-%lu");
		static const char * const fmt2("Range: bytes=%lu-");

		char range_line[64];

#if LL_WINDOWS
		_snprintf_s(range_line, sizeof(range_line), sizeof(range_line) - 1,
					(mReqLength ? fmt1 : fmt2),
					(unsigned long) mReqOffset, (unsigned long) (mReqOffset + mReqLength - 1));
#else
		snprintf(range_line, sizeof(range_line),
				 (mReqLength ? fmt1 : fmt2),
				 (unsigned long) mReqOffset, (unsigned long) (mReqOffset + mReqLength - 1));
#endif // LL_WINDOWS
		range_line[sizeof(range_line) - 1] = '\0';
		mCurlHeaders = curl_slist_append(mCurlHeaders, range_line);
	}

	mCurlHeaders = curl_slist_append(mCurlHeaders, "Pragma:");

	// Request options
	long timeout(HTTP_REQUEST_TIMEOUT_DEFAULT);
	if (mReqOptions)
	{
		timeout = mReqOptions->getTimeout();
		timeout = llclamp(timeout, HTTP_REQUEST_TIMEOUT_MIN, HTTP_REQUEST_TIMEOUT_MAX);
	}
	curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, timeout);
	curl_easy_setopt(mCurlHandle, CURLOPT_CONNECTTIMEOUT, timeout);

	// Request headers
	if (mReqHeaders)
	{
		// Caller's headers last to override
		mCurlHeaders = append_headers_to_slist(mReqHeaders, mCurlHeaders);
	}
	curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mCurlHeaders);

	if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS))
	{
		curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback);
		curl_easy_setopt(mCurlHandle, CURLOPT_HEADERDATA, this);
	}
	
	if (status)
	{
		mCurlService = service;
	}
	return status;
}


size_t HttpOpRequest::writeCallback(void * data, size_t size, size_t nmemb, void * userdata)
{
	HttpOpRequest * op(static_cast<HttpOpRequest *>(userdata));

	if (! op->mReplyBody)
	{
		op->mReplyBody = new BufferArray();
	}
	const size_t req_size(size * nmemb);
	const size_t write_size(op->mReplyBody->append(static_cast<char *>(data), req_size));
	return write_size;
}

		
size_t HttpOpRequest::readCallback(void * data, size_t size, size_t nmemb, void * userdata)
{
	HttpOpRequest * op(static_cast<HttpOpRequest *>(userdata));

	if (! op->mReqBody)
	{
		return 0;
	}
	const size_t req_size(size * nmemb);
	const size_t body_size(op->mReqBody->size());
	if (body_size <= op->mCurlBodyPos)
	{
		if (body_size < op->mCurlBodyPos)
		{
			// Warn but continue if the read position moves beyond end-of-body
			// for some reason.
			LL_WARNS("HttpCore") << "Request body position beyond body size.  Truncating request body."
								 << LL_ENDL;
		}
		return 0;
	}

	const size_t do_size((std::min)(req_size, body_size - op->mCurlBodyPos));
	const size_t read_size(op->mReqBody->read(op->mCurlBodyPos, static_cast<char *>(data), do_size));
	op->mCurlBodyPos += read_size;
	return read_size;
}

		
size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, void * userdata)
{
	static const char status_line[] = "HTTP/";
	static const size_t status_line_len = sizeof(status_line) - 1;

	static const char con_ran_line[] = "content-range:";
	static const size_t con_ran_line_len = sizeof(con_ran_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
		// taking results from the last header stanza we receive.
		op->mReplyOffset = 0;
		op->mReplyLength = 0;
		op->mReplyFullLength = 0;
		op->mStatus = HttpStatus();
		if (op->mReplyHeaders)
		{
			op->mReplyHeaders->mHeaders.clear();
		}
	}

	// 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)(wanted_hdr_size, sizeof(hdr_buffer) - 1));
		
		memcpy(hdr_buffer, hdr_data, frag_size);
		hdr_buffer[frag_size] = '\0';
		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 (! (status = parse_content_range_header(hdr_buffer, &first, &last, &length)))
			{
				// Success, record the fragment position
				op->mReplyOffset = first;
				op->mReplyLength = last - first + 1;
				op->mReplyFullLength = length;
			}
			else if (-1 == status)
			{
				// Response is badly formed and shouldn't be accepted
				op->mStatus = HttpStatus(HttpStatus::LLCORE, HE_INV_CONTENT_RANGE_HDR);
			}
			else
			{
				// Ignore the unparsable.
				LL_INFOS_ONCE("CoreHttp") << "Problem parsing odd Content-Range header:  '"
										  << std::string(hdr_data, frag_size)
										  << "'.  Ignoring."
										  << LL_ENDL;
			}
		}
	}

	return hdr_size;
}


int HttpOpRequest::debugCallback(CURL * handle, curl_infotype info, char * buffer, size_t len, void * userdata)
{
	HttpOpRequest * op(static_cast<HttpOpRequest *>(userdata));

	std::string safe_line;
	std::string tag;
	bool logit(false);
	const size_t log_len((std::min)(len, size_t(256)));		// Keep things reasonable in all cases
	
	switch (info)
	{
	case CURLINFO_TEXT:
		if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)
		{
			tag = "TEXT";
			escape_libcurl_debug_data(buffer, log_len, true, safe_line);
			logit = true;
		}
		break;
			
	case CURLINFO_HEADER_IN:
		if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)
		{
			tag = "HEADERIN";
			escape_libcurl_debug_data(buffer, log_len, true, safe_line);
			logit = true;
		}
		break;
			
	case CURLINFO_HEADER_OUT:
		if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)
		{
			tag = "HEADEROUT";
			escape_libcurl_debug_data(buffer, log_len, true, safe_line);	// Goes out as one line unlike header_in
			logit = true;
		}
		break;
			
	case CURLINFO_DATA_IN:
		if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)
		{
			tag = "DATAIN";
			logit = true;
			if (op->mTracing >= HTTP_TRACE_CURL_BODIES)
			{
				escape_libcurl_debug_data(buffer, log_len, false, safe_line);
			}
			else
			{
				std::ostringstream out;
				out << len << " Bytes";
				safe_line = out.str();
			}
		}
		break;
			
	case CURLINFO_DATA_OUT:
		if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)
		{
			tag = "DATAOUT";
			logit = true;
			if (op->mTracing >= HTTP_TRACE_CURL_BODIES)
			{
				escape_libcurl_debug_data(buffer, log_len, false, safe_line);
			}
			else
			{
				std::ostringstream out;
				out << len << " Bytes";
				safe_line = out.str();
			}
		}
		break;
			
	default:
		logit = false;
		break;
	}

	if (logit)
	{
		LL_INFOS("CoreHttp") << "TRACE, LibcurlDebug, Handle:  "
							 << static_cast<HttpHandle>(op)
							 << ", Type:  " << tag
							 << ", Data:  " << safe_line
							 << LL_ENDL;
	}
		
	return 0;
}


}   // end namespace LLCore


// =======================================
// Anonymous Namespace
// =======================================

namespace
{

int parse_content_range_header(char * buffer,
							   unsigned int * first,
							   unsigned int * last,
							   unsigned int * length)
{
	char * tok_state(NULL), * tok(NULL);
	bool match(true);
			
	if (! os_strtok_r(buffer, hdr_separator, &tok_state))
		match = false;
	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)
	{
		unsigned int lcl_first(0), lcl_last(0), lcl_len(0);

#if LL_WINDOWS
		if (3 == sscanf_s(tok, "%u-%u/%u", &lcl_first, &lcl_last, &lcl_len))
#else
		if (3 == sscanf(tok, "%u-%u/%u", &lcl_first, &lcl_last, &lcl_len))
#endif // LL_WINDOWS
		{
			if (lcl_first > lcl_last || lcl_last >= lcl_len)
				return -1;
			*first = lcl_first;
			*last = lcl_last;
			*length = lcl_len;
			return 0;
		}
#if LL_WINDOWS
		if (2 == sscanf_s(tok, "%u-%u/*", &lcl_first, &lcl_last))
#else
		if (2 == sscanf(tok, "%u-%u/*", &lcl_first, &lcl_last))
#endif	// LL_WINDOWS
		{
			if (lcl_first > lcl_last)
				return -1;
			*first = lcl_first;
			*last = lcl_last;
			*length = 0;
			return 0;
		}
	}

	// Header is there but badly/unexpectedly formed, try to ignore it.
	return 1;
}


void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub, std::string & safe_line)
{
	std::string out;
	len = (std::min)(len, size_t(200));
	out.reserve(3 * len);
	for (int i(0); i < len; ++i)
	{
		unsigned char uc(static_cast<unsigned char>(buffer[i]));

		if (uc < 32 || uc > 126)
		{
			if (scrub)
			{
				out.append(1, ' ');
			}
			else
			{
				static const char hex[] = "0123456789ABCDEF";
				char convert[4];

				convert[0] = '%';
				convert[1] = hex[(uc >> 4) % 16];
				convert[2] = hex[uc % 16];
				convert[3] = '\0';
				out.append(convert);
			}
		}
		else
		{
			out.append(1, buffer[i]);
		}
	}
	safe_line.swap(out);
}


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