/** 
 * @file llhttpassetstorage.cpp
 * @brief Subclass capable of loading asset data to/from an external
 * source. Currently, a web server accessed via curl
 *
 * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc.
 * $License$
 */

#include "linden_common.h"

#include "llhttpassetstorage.h"

#include "indra_constants.h"
#include "llvfile.h"
#include "llvfs.h"

#include "zlib/zlib.h"

const F32 MAX_PROCESSING_TIME = 0.005f;
const S32 CURL_XFER_BUFFER_SIZE = 65536;
// Try for 30 minutes for now.
const F32 GET_URL_TO_FILE_TIMEOUT = 1800.0f;

const S32 COMPRESSED_INPUT_BUFFER_SIZE = 4096;

const S32 HTTP_OK = 200;
const S32 HTTP_PUT_OK = 201;
const S32 HTTP_NO_CONTENT = 204;
const S32 HTTP_MISSING = 404;
const S32 HTTP_SERVER_BAD_GATEWAY = 502;
const S32 HTTP_SERVER_TEMP_UNAVAILABLE = 503;

/////////////////////////////////////////////////////////////////////////////////
// LLTempAssetData
// An asset not stored on central asset store, but on a simulator node somewhere.
/////////////////////////////////////////////////////////////////////////////////
struct LLTempAssetData
{
	LLUUID	mAssetID;
	LLUUID	mAgentID;
	std::string	mHostName;
};

/////////////////////////////////////////////////////////////////////////////////
// LLHTTPAssetRequest
/////////////////////////////////////////////////////////////////////////////////

class LLHTTPAssetRequest : public LLAssetRequest
{
public:
	LLHTTPAssetRequest(LLHTTPAssetStorage *asp, const LLUUID &uuid, LLAssetType::EType type, const char *url, CURLM *curl_multi);
	virtual ~LLHTTPAssetRequest();
	
	void setupCurlHandle();

	void   	prepareCompressedUpload();
	void	finishCompressedUpload();
	size_t	readCompressedData(void* data, size_t size);

	static size_t curlCompressedUploadCallback(
					void *data, size_t size, size_t nmemb, void *user_data);

public:
	LLHTTPAssetStorage *mAssetStoragep;

	CURL  *mCurlHandle;
	CURLM *mCurlMultiHandle;
	char  *mURLBuffer;
	struct curl_slist *mHTTPHeaders;
	LLVFile *mVFile;
	LLUUID  mTmpUUID;
	BOOL    mIsUpload;
	BOOL	mIsLocalUpload;
	BOOL	mIsDownload;

	bool		mZInitialized;
	z_stream	mZStream;
	char*		mZInputBuffer;
	bool		mZInputExhausted;

	FILE *mFP;
};


LLHTTPAssetRequest::LLHTTPAssetRequest(LLHTTPAssetStorage *asp, const LLUUID &uuid, LLAssetType::EType type, const char *url, CURLM *curl_multi)
	: LLAssetRequest(uuid, type),
	mZInitialized(false)
{
	mAssetStoragep = asp;
	mCurlHandle = NULL;
	mCurlMultiHandle = curl_multi;
	mVFile = NULL;
	mIsUpload = FALSE;
	mIsLocalUpload = FALSE;
	mIsDownload = FALSE;
	mHTTPHeaders = NULL;
	
	mURLBuffer = new char[strlen(url) + 1]; /*Flawfinder: ignore*/
	if (mURLBuffer)
	{
	    strcpy(mURLBuffer, url);
	}
}

LLHTTPAssetRequest::~LLHTTPAssetRequest()
{
	// Cleanup/cancel the request
	if (mCurlHandle)
	{
		curl_multi_remove_handle(mCurlMultiHandle, mCurlHandle);
		curl_easy_cleanup(mCurlHandle);
		if (mAssetStoragep)
		{
			// Terminating a request.  Thus upload or download is no longer pending.
			if (mIsUpload)
			{
				mAssetStoragep->clearPendingUpload();
			}
			else if (mIsLocalUpload)
			{
				mAssetStoragep->clearPendingLocalUpload();
			}
			else if (mIsDownload)
			{
				mAssetStoragep->clearPendingDownload();
			}
			else
			{
				llerrs << "LLHTTPAssetRequest::~LLHTTPAssetRequest - Destroyed request is not upload OR download, this is bad!" << llendl;
			}
		}
		else
		{
			llerrs << "LLHTTPAssetRequest::~LLHTTPAssetRequest - No asset storage associated with this request!" << llendl;
		}
	}
	if (mHTTPHeaders)
	{
		curl_slist_free_all(mHTTPHeaders);
	}
	delete[] mURLBuffer;
	delete   mVFile;
	finishCompressedUpload();
}

void LLHTTPAssetRequest::setupCurlHandle()
{
	mCurlHandle = curl_easy_init();
	curl_easy_setopt(mCurlHandle, CURLOPT_NOSIGNAL, 1);
	curl_easy_setopt(mCurlHandle, CURLOPT_NOPROGRESS, 1);
	curl_easy_setopt(mCurlHandle, CURLOPT_URL, mURLBuffer);
	curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this);
	if (mIsDownload)
	{
		curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, "");
		// only do this on downloads, as uploads 
		// to some apache configs (like our test grids)
		// mistakenly claim the response is gzip'd if the resource
		// name ends in .gz, even though in a PUT, the response is
		// just plain HTML saying "created"
	}
	/* Remove the Pragma: no-cache header that libcurl inserts by default;
	   we want the cached version, if possible. */
	if (mZInitialized)
	{
		curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, "");
			// disable use of proxy, which can't handle chunked transfers
	}
	mHTTPHeaders = curl_slist_append(mHTTPHeaders, "Pragma:");
	// resist the temptation to explicitly add the Transfer-Encoding: chunked
	// header here - invokes a libCURL bug
	curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mHTTPHeaders);
	if (mAssetStoragep)
	{
		// Set the appropriate pending upload or download flag
		if (mIsUpload)
		{
			mAssetStoragep->setPendingUpload();
		}
		else if (mIsLocalUpload)
		{
			mAssetStoragep->setPendingLocalUpload();
		}
		else if (mIsDownload)
		{
			mAssetStoragep->setPendingDownload();
		}
		else
		{
			llerrs << "LLHTTPAssetRequest::setupCurlHandle - Request is not upload OR download, this is bad!" << llendl;
		}
	}
	else
	{
		llerrs << "LLHTTPAssetRequest::setupCurlHandle - No asset storage associated with this request!" << llendl;
	}
}

void LLHTTPAssetRequest::prepareCompressedUpload()
{
	mZStream.next_in = Z_NULL;
	mZStream.avail_in = 0;
	mZStream.zalloc = Z_NULL;
	mZStream.zfree = Z_NULL;
	mZStream.opaque = Z_NULL;

	int r = deflateInit2(&mZStream,
			1,			// compression level
			Z_DEFLATED,	// the only method defined
			15 + 16,	// the default windowBits + gzip header flag
			8,			// the default memLevel
			Z_DEFAULT_STRATEGY);

	if (r != Z_OK)
	{
		llerrs << "LLHTTPAssetRequest::prepareCompressedUpload defalateInit2() failed" << llendl;
	}

	mZInitialized = true;
	mZInputBuffer = new char[COMPRESSED_INPUT_BUFFER_SIZE];
	mZInputExhausted = false;

	mVFile = new LLVFile(gAssetStorage->mVFS,
					getUUID(), getType(), LLVFile::READ);
}

void LLHTTPAssetRequest::finishCompressedUpload()
{
	if (mZInitialized)
	{
		llinfos << "LLHTTPAssetRequest::finishCompressedUpload: "
			<< "read " << mZStream.total_in << " byte asset file, "
			<< "uploaded " << mZStream.total_out << " byte compressed asset"
			<< llendl;

		deflateEnd(&mZStream);
		delete[] mZInputBuffer;
	}
}

size_t LLHTTPAssetRequest::readCompressedData(void* data, size_t size)
{
	mZStream.next_out = (Bytef*)data;
	mZStream.avail_out = size;

	while (mZStream.avail_out > 0)
	{
		if (mZStream.avail_in == 0  &&  !mZInputExhausted)
		{
			S32 to_read = llmin(COMPRESSED_INPUT_BUFFER_SIZE,
							(S32)(mVFile->getSize() - mVFile->tell()));
			
			mVFile->read((U8*)mZInputBuffer, to_read); /*Flawfinder: ignore*/

			mZStream.next_in = (Bytef*)mZInputBuffer;
			mZStream.avail_in = mVFile->getLastBytesRead();

			mZInputExhausted = mZStream.avail_in == 0;
		}

		int r = deflate(&mZStream,
					mZInputExhausted ? Z_FINISH : Z_NO_FLUSH);

		if (r == Z_STREAM_END)
		{
			break;
		}
	}

	return size - mZStream.avail_out;
}

//static
size_t LLHTTPAssetRequest::curlCompressedUploadCallback(
		void *data, size_t size, size_t nmemb, void *user_data)
{
	if (!gAssetStorage)
	{
		return 0;
	}
	CURL *curl_handle = (CURL *)user_data;
	LLHTTPAssetRequest *req = NULL;
	curl_easy_getinfo(curl_handle, CURLINFO_PRIVATE, &req);

	return req->readCompressedData(data, size * nmemb);
}

/////////////////////////////////////////////////////////////////////////////////
// LLHTTPAssetStorage
/////////////////////////////////////////////////////////////////////////////////


LLHTTPAssetStorage::LLHTTPAssetStorage(LLMessageSystem *msg, LLXferManager *xfer,
									 LLVFS *vfs, const LLHost &upstream_host,
									 const char *web_host,
									 const char *local_web_host,
									 const char *host_name)
	: LLAssetStorage(msg, xfer, vfs, upstream_host)
{
	_init(web_host, local_web_host, host_name);
}

LLHTTPAssetStorage::LLHTTPAssetStorage(LLMessageSystem *msg, LLXferManager *xfer,
									   LLVFS *vfs,
									   const char *web_host,
									   const char *local_web_host,
									   const char *host_name)
	: LLAssetStorage(msg, xfer, vfs)
{
	_init(web_host, local_web_host, host_name);
}

void LLHTTPAssetStorage::_init(const char *web_host, const char *local_web_host, const char* host_name)
{
	mBaseURL = web_host;
	mLocalBaseURL = local_web_host;
	mHostName = host_name;

	// Do not change this "unless you are familiar with and mean to control 
	// internal operations of libcurl"
	// - http://curl.haxx.se/libcurl/c/curl_global_init.html
	curl_global_init(CURL_GLOBAL_ALL);

	mCurlMultiHandle = curl_multi_init();

	mPendingDownload = FALSE;
	mPendingUpload = FALSE;
	mPendingLocalUpload = FALSE;
}

LLHTTPAssetStorage::~LLHTTPAssetStorage()
{
	curl_multi_cleanup(mCurlMultiHandle);
	mCurlMultiHandle = NULL;
	
	curl_global_cleanup();
}

// storing data is simpler than getting it, so we just overload the whole method
void LLHTTPAssetStorage::storeAssetData(
	const LLUUID& uuid,
	LLAssetType::EType type,
	LLAssetStorage::LLStoreAssetCallback callback,
	void* user_data,
	bool temp_file,
	bool is_priority,
	bool store_local,
	const LLUUID& requesting_agent_id)
{
	if (mVFS->getExists(uuid, type))
	{
		LLAssetRequest *req = new LLAssetRequest(uuid, type);
		req->mUpCallback = callback;
		req->mUserData = user_data;
		req->mRequestingAgentID = requesting_agent_id;

		// this will get picked up and transmitted in checkForTimeouts
		if(store_local)
		{
			mPendingLocalUploads.push_back(req);
		}
		else if(is_priority)
		{
			mPendingUploads.push_front(req);
		}
		else
		{
			mPendingUploads.push_back(req);
		}
	}
	else
	{
		llwarns << "AssetStorage: attempt to upload non-existent vfile " << uuid << ":" << LLAssetType::lookup(type) << llendl;
		if (callback)
		{
			callback(uuid, user_data,  LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE );
		}
	}
}

// virtual
void LLHTTPAssetStorage::storeAssetData(
	const char* filename,
	const LLUUID& asset_id,
	LLAssetType::EType asset_type,
	LLStoreAssetCallback callback,
	void* user_data,
	bool temp_file,
	bool is_priority)
{
	llinfos << "LLAssetStorage::storeAssetData (legacy)" << asset_id << ":" << LLAssetType::lookup(asset_type) << llendl;

	LLLegacyAssetRequest *legacy = new LLLegacyAssetRequest;

	legacy->mUpCallback = callback;
	legacy->mUserData = user_data;

	FILE *fp = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/
	if (fp)
	{
		LLVFile file(mVFS, asset_id, asset_type, LLVFile::WRITE);

		fseek(fp, 0, SEEK_END);
		S32 size = ftell(fp);
		fseek(fp, 0, SEEK_SET);

		file.setMaxSize(size);

		const S32 buf_size = 65536;
		U8 copy_buf[buf_size];
		while ((size = (S32)fread(copy_buf, 1, buf_size, fp)))
		{
			file.write(copy_buf, size);
		}
		fclose(fp);

		// if this upload fails, the caller needs to setup a new tempfile for us
		if (temp_file)
		{
			LLFile::remove(filename);
		}
		
		storeAssetData(
			asset_id,
			asset_type,
			legacyStoreDataCallback,
			(void**)legacy,
			temp_file,
			is_priority);
	}
	else
	{
		if (callback)
		{
			callback(LLUUID::null, user_data, LL_ERR_CANNOT_OPEN_FILE);
		}
	}
}

// internal requester, used by getAssetData in superclass
void LLHTTPAssetStorage::_queueDataRequest(const LLUUID& uuid, LLAssetType::EType type,
										  void (*callback)(LLVFS *vfs, const LLUUID&, LLAssetType::EType, void *, S32),
										  void *user_data, BOOL duplicate,
										   BOOL is_priority)
{
	// stash the callback info so we can find it after we get the response message
	LLAssetRequest *req = new LLAssetRequest(uuid, type);
	req->mDownCallback = callback;
	req->mUserData = user_data;
	req->mIsPriority = is_priority;

	// this will get picked up and downloaded in checkForTimeouts

	//
	// HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACK!  Asset requests were taking too long and timing out.
	// Since texture requests are the LEAST sensitive (on the simulator) to being delayed, add
	// non-texture requests to the front, and add texture requests to the back.  The theory is
	// that we always want them first, even if they're out of order.
	//
	
	if (req->getType() == LLAssetType::AT_TEXTURE)
	{
		mPendingDownloads.push_back(req);
	}
	else
	{
		mPendingDownloads.push_front(req);
	}
}

// overloaded to additionally move data to/from the webserver
void LLHTTPAssetStorage::checkForTimeouts()
{
	LLAssetRequest *req = NULL;
	if (mPendingDownloads.size() > 0  && !mPendingDownload)
	{	
		req = mPendingDownloads.front();
		// Setup this curl download request
		// We need to generate a new request here
		// since the one in the list could go away
		char tmp_url[MAX_STRING]; /*Flawfinder: ignore*/
		char uuid_str[UUID_STR_LENGTH]; /*Flawfinder: ignore*/
		req->getUUID().toString(uuid_str);
		std::string base_url = getBaseURL(req->getUUID(), req->getType());
		snprintf(tmp_url, sizeof(tmp_url), "%s/%36s.%s", base_url.c_str() , uuid_str, LLAssetType::lookup(req->getType())); /*Flawfinder: ignore*/

		LLHTTPAssetRequest *new_req = new LLHTTPAssetRequest(this, req->getUUID(), req->getType(), tmp_url, mCurlMultiHandle);
		new_req->mTmpUUID.generate();
		new_req->mIsDownload = TRUE;

		// Sets pending download flag internally
		new_req->setupCurlHandle();
		curl_easy_setopt(new_req->mCurlHandle, CURLOPT_FOLLOWLOCATION, TRUE);
		curl_easy_setopt(new_req->mCurlHandle, CURLOPT_WRITEFUNCTION, &curlDownCallback);
		curl_easy_setopt(new_req->mCurlHandle, CURLOPT_WRITEDATA, new_req->mCurlHandle);
	
		curl_multi_add_handle(mCurlMultiHandle, new_req->mCurlHandle);
		llinfos << "Requesting " << new_req->mURLBuffer << llendl;

	}

	
	if (mPendingUploads.size() > 0 && !mPendingUpload)
	{
		req = mPendingUploads.front();
		// setup this curl upload request

		bool do_compress = req->getType() == LLAssetType::AT_OBJECT;

		char tmp_url[MAX_STRING];/*Flawfinder: ignore*/
		char uuid_str[UUID_STR_LENGTH];/*Flawfinder: ignore*/
		req->getUUID().toString(uuid_str);
		snprintf(tmp_url, sizeof(tmp_url), 					/*Flawfinder: ignore*/
				do_compress ? "%s/%s.%s.gz" : "%s/%s.%s",
				mBaseURL.c_str(), uuid_str, LLAssetType::lookup(req->getType())); 

		LLHTTPAssetRequest *new_req = new LLHTTPAssetRequest(this, req->getUUID(), req->getType(), tmp_url, mCurlMultiHandle);
		new_req->mIsUpload = TRUE;
		if (do_compress)
		{
			new_req->prepareCompressedUpload();
		}

		// Sets pending upload flag internally
		new_req->setupCurlHandle();
		curl_easy_setopt(new_req->mCurlHandle, CURLOPT_UPLOAD, 1);
		curl_easy_setopt(new_req->mCurlHandle, CURLOPT_WRITEFUNCTION, &nullOutputCallback);

		if (do_compress)
		{
			curl_easy_setopt(new_req->mCurlHandle, CURLOPT_READFUNCTION,
					&LLHTTPAssetRequest::curlCompressedUploadCallback);
		}
		else
		{
			LLVFile file(mVFS, req->getUUID(), req->getType());
			curl_easy_setopt(new_req->mCurlHandle, CURLOPT_INFILESIZE, file.getSize());
			curl_easy_setopt(new_req->mCurlHandle, CURLOPT_READFUNCTION,
					&curlUpCallback);
		}
		curl_easy_setopt(new_req->mCurlHandle, CURLOPT_READDATA, new_req->mCurlHandle);
	
		curl_multi_add_handle(mCurlMultiHandle, new_req->mCurlHandle);
		llinfos << "Requesting PUT " << new_req->mURLBuffer << llendl;
		// Pending upload will have been flagged by the request
	}


	if (mPendingLocalUploads.size() > 0 && !mPendingLocalUpload)
	{
		req = mPendingLocalUploads.front();
		// setup this curl upload request
		LLVFile file(mVFS, req->getUUID(), req->getType());

		char tmp_url[MAX_STRING]; /*Flawfinder: ignore*/
		char uuid_str[UUID_STR_LENGTH]; /*Flawfinder: ignore*/
		req->getUUID().toString(uuid_str);
		
		// KLW - All temporary uploads are saved locally "http://localhost:12041/asset"
		snprintf(tmp_url, sizeof(tmp_url), "%s/%36s.%s", mLocalBaseURL.c_str(), uuid_str, LLAssetType::lookup(req->getType())); /*Flawfinder: ignore*/

		LLHTTPAssetRequest *new_req = new LLHTTPAssetRequest(this, req->getUUID(), req->getType(), tmp_url, mCurlMultiHandle);
		new_req->mIsLocalUpload = TRUE;
		new_req->mRequestingAgentID = req->mRequestingAgentID;

		// Sets pending upload flag internally
		new_req->setupCurlHandle();
		curl_easy_setopt(new_req->mCurlHandle, CURLOPT_PUT, 1);
		curl_easy_setopt(new_req->mCurlHandle, CURLOPT_INFILESIZE, file.getSize());
		curl_easy_setopt(new_req->mCurlHandle, CURLOPT_WRITEFUNCTION, &nullOutputCallback);
		curl_easy_setopt(new_req->mCurlHandle, CURLOPT_READFUNCTION, &curlUpCallback);
		curl_easy_setopt(new_req->mCurlHandle, CURLOPT_READDATA, new_req->mCurlHandle);
	
		curl_multi_add_handle(mCurlMultiHandle, new_req->mCurlHandle);
		llinfos << "TAT: LLHTTPAssetStorage::checkForTimeouts() : pending local!"
			<< " Requesting PUT " << new_req->mURLBuffer << llendl;
		// Pending upload will have been flagged by the request
	}
	S32 count = 0;
	CURLMcode mcode;
	int queue_length;
	do
	{
		mcode = curl_multi_perform(mCurlMultiHandle, &queue_length);
		count++;
	} while (mcode == CURLM_CALL_MULTI_PERFORM && (count < 5));

	CURLMsg *curl_msg;
	do
	{
		curl_msg = curl_multi_info_read(mCurlMultiHandle, &queue_length);
		if (curl_msg && curl_msg->msg == CURLMSG_DONE)
		{
			long curl_result = 0;
			S32 xfer_result = 0;

			LLHTTPAssetRequest *req = NULL;
			curl_easy_getinfo(curl_msg->easy_handle, CURLINFO_PRIVATE, &req);
								
			curl_easy_getinfo(curl_msg->easy_handle, CURLINFO_HTTP_CODE, &curl_result);
			if (req->mIsUpload || req->mIsLocalUpload)
			{
				if (curl_msg->data.result == CURLE_OK && (curl_result == HTTP_OK || curl_result == HTTP_PUT_OK || curl_result == HTTP_NO_CONTENT))
				{
					llinfos << "Success uploading " << req->getUUID() << " to " << req->mURLBuffer << llendl;
					if (req->mIsLocalUpload)
					{
						addTempAssetData(req->getUUID(), req->mRequestingAgentID, mHostName);
					}
				}
				else if (curl_msg->data.result == CURLE_COULDNT_CONNECT ||
						curl_msg->data.result == CURLE_OPERATION_TIMEOUTED ||
						curl_result == HTTP_SERVER_BAD_GATEWAY ||
						curl_result == HTTP_SERVER_TEMP_UNAVAILABLE)
				{
					llwarns << "Re-requesting upload for " << req->getUUID() << ".  Received upload error to " << req->mURLBuffer <<
						" with result " << curl_easy_strerror(curl_msg->data.result) << ", http result " << curl_result << llendl;
				}
				else
				{
					llwarns << "Failure uploading " << req->getUUID() << " to " << req->mURLBuffer <<
						" with result " << curl_easy_strerror(curl_msg->data.result) << ", http result " << curl_result << llendl;

					xfer_result = LL_ERR_ASSET_REQUEST_FAILED;
				}

				if (!(curl_msg->data.result == CURLE_COULDNT_CONNECT ||
						curl_msg->data.result == CURLE_OPERATION_TIMEOUTED ||
						curl_result == HTTP_SERVER_BAD_GATEWAY ||
						curl_result == HTTP_SERVER_TEMP_UNAVAILABLE))
				{
					// shared upload finished callback
					// in the base class, this is called from processUploadComplete
					_callUploadCallbacks(req->getUUID(), req->getType(), (xfer_result == 0));
					// Pending upload flag will get cleared when the request is deleted
				}
			}
			else if (req->mIsDownload)
			{
				if (curl_result == HTTP_OK && curl_msg->data.result == CURLE_OK)
				{
					if (req->mVFile && req->mVFile->getSize() > 0)
					{					
						llinfos << "Success downloading " << req->mURLBuffer << ", size " << req->mVFile->getSize() << llendl;

						req->mVFile->rename(req->getUUID(), req->getType());
					}
					else
					{
						// TODO: if this actually indicates a bad asset on the server
						// (not certain at this point), then delete it
						llwarns << "Found " << req->mURLBuffer << " to be zero size" << llendl;
						xfer_result = LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE;
					}
				}
				else
				{
					// KLW - TAT See if an avatar owns this texture, and if so request re-upload.
					llwarns << "Failure downloading " << req->mURLBuffer << 
						" with result " << curl_easy_strerror(curl_msg->data.result) << ", http result " << curl_result << llendl;

					xfer_result = (curl_result == HTTP_MISSING) ? LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE : LL_ERR_ASSET_REQUEST_FAILED;

					if (req->mVFile)
					{
						req->mVFile->remove();
					}
				}

				// call the static callback for transfer completion
				// this will cleanup all requests for this asset, including ours
				downloadCompleteCallback(xfer_result, (void *)req);
				// Pending download flag will get cleared when the request is deleted
			}
			else
			{
				// nothing, just axe this request
				// currently this can only mean an asset delete
			}

			// Deleting clears the pending upload/download flag if it's set and the request is transferring
			delete req;
			req = NULL;
		}

	} while (curl_msg && queue_length > 0);
	

	LLAssetStorage::checkForTimeouts();
}

// static
size_t LLHTTPAssetStorage::curlDownCallback(void *data, size_t size, size_t nmemb, void *user_data)
{
	if (!gAssetStorage)
	{
		llwarns << "Missing gAssetStorage, aborting curl download callback!" << llendl;
		return 0;
	}
	S32 bytes = (S32)(size * nmemb);
	CURL *curl_handle = (CURL *)user_data;
	LLHTTPAssetRequest *req = NULL;
	curl_easy_getinfo(curl_handle, CURLINFO_PRIVATE, &req);

	if (! req->mVFile)
	{
		req->mVFile = new LLVFile(gAssetStorage->mVFS, req->mTmpUUID, LLAssetType::AT_NONE, LLVFile::APPEND);
	}

	double content_length = 0.0;
	curl_easy_getinfo(curl_handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length);

	// sanitize content_length, reconcile w/ actual data
	S32 file_length = llmax(0, (S32)llmin(content_length, 20000000.0), bytes + req->mVFile->getSize());

	req->mVFile->setMaxSize(file_length);
	req->mVFile->write((U8*)data, bytes);

	return nmemb;
}

// static 
size_t LLHTTPAssetStorage::curlUpCallback(void *data, size_t size, size_t nmemb, void *user_data)
{
	if (!gAssetStorage)
	{
		llwarns << "Missing gAssetStorage, aborting curl download callback!" << llendl;
		return 0;
	}
	CURL *curl_handle = (CURL *)user_data;
	LLHTTPAssetRequest *req = NULL;
	curl_easy_getinfo(curl_handle, CURLINFO_PRIVATE, &req);

	if (! req->mVFile)
	{
		req->mVFile = new LLVFile(gAssetStorage->mVFS, req->getUUID(), req->getType(), LLVFile::READ);
	}

	S32 bytes = llmin((S32)(size * nmemb), (S32)(req->mVFile->getSize() - req->mVFile->tell()));

	req->mVFile->read((U8*)data, bytes);/*Flawfinder: ignore*/

	return req->mVFile->getLastBytesRead();
}

// static
size_t LLHTTPAssetStorage::nullOutputCallback(void *data, size_t size, size_t nmemb, void *user_data)
{
	// do nothing, this is here to soak up script output so it doesn't end up on stdout

	return nmemb;
}



// blocking asset fetch which bypasses the VFS
// this is a very limited function for use by the simstate loader and other one-offs
S32 LLHTTPAssetStorage::getURLToFile(const LLUUID& uuid, LLAssetType::EType asset_type, const LLString &url, const char *filename, progress_callback callback, void *userdata)
{
	// FIXME: There is no guarantee that the uuid and the asset_type match
	// - not that it matters. - Doug
	lldebugs << "LLHTTPAssetStorage::getURLToFile() - " << url << llendl;

	FILE *fp = LLFile::fopen(filename, "wb"); /*Flawfinder: ignore*/
	if (! fp)
	{
		llwarns << "Failed to open " << filename << " for writing" << llendl;
		return LL_ERR_ASSET_REQUEST_FAILED;
	}

	// make sure we use the normal curl setup, even though we don't really need a request object
	LLHTTPAssetRequest req(this, uuid, asset_type, url.c_str(), mCurlMultiHandle);
	req.mFP = fp;
	req.mIsDownload = TRUE;
	
	req.setupCurlHandle();
	curl_easy_setopt(req.mCurlHandle, CURLOPT_FOLLOWLOCATION, TRUE);
	curl_easy_setopt(req.mCurlHandle, CURLOPT_WRITEFUNCTION, &curlFileDownCallback);
	curl_easy_setopt(req.mCurlHandle, CURLOPT_WRITEDATA, req.mCurlHandle);

	curl_multi_add_handle(mCurlMultiHandle, req.mCurlHandle);
	llinfos << "Requesting as file " << req.mURLBuffer << llendl;

	// braindead curl loop
	int queue_length;
	CURLMsg *curl_msg;
	LLTimer timeout;
	timeout.setTimerExpirySec(GET_URL_TO_FILE_TIMEOUT);
	bool success = false;
	S32 xfer_result = 0;
	do
	{
		curl_multi_perform(mCurlMultiHandle, &queue_length);
		curl_msg = curl_multi_info_read(mCurlMultiHandle, &queue_length);

		if (callback)
		{
			callback(userdata);
		}

		if ( curl_msg && (CURLMSG_DONE == curl_msg->msg) )
		{
			success = true;
		}
		else if (timeout.hasExpired())
		{
			llwarns << "Request for " << url << " has timed out." << llendl;
			success = false;
			xfer_result = LL_ERR_ASSET_REQUEST_FAILED;
			break;
		}
	} while (!success);

	if (success)
	{
		long curl_result = 0;
		curl_easy_getinfo(curl_msg->easy_handle, CURLINFO_HTTP_CODE, &curl_result);
		
		if (curl_result == HTTP_OK && curl_msg->data.result == CURLE_OK)
		{
			S32 size = ftell(req.mFP);
			if (size > 0)
			{
				// everything seems to be in order
				llinfos << "Success downloading " << req.mURLBuffer << " to file, size " << size << llendl;
			}
			else
			{
				llwarns << "Found " << req.mURLBuffer << " to be zero size" << llendl;
				xfer_result = LL_ERR_ASSET_REQUEST_FAILED;
			}
		}
		else
		{
			xfer_result = curl_result == HTTP_MISSING ? LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE : LL_ERR_ASSET_REQUEST_FAILED;
			llinfos << "Failure downloading " << req.mURLBuffer << 
				" with result " << curl_easy_strerror(curl_msg->data.result) << ", http result " << curl_result << llendl;
		}
	}

	fclose(fp);
	if (xfer_result)
	{
		LLFile::remove(filename);
	}
	return xfer_result;
}


// static
size_t LLHTTPAssetStorage::curlFileDownCallback(void *data, size_t size, size_t nmemb, void *user_data)
{	
	CURL *curl_handle = (CURL *)user_data;
	LLHTTPAssetRequest *req = NULL;
	curl_easy_getinfo(curl_handle, CURLINFO_PRIVATE, &req);

	if (! req->mFP)
	{
		llwarns << "Missing mFP, aborting curl file download callback!" << llendl;
		return 0;
	}

	return fwrite(data, size, nmemb, req->mFP);
}

// virtual 
void LLHTTPAssetStorage::addTempAssetData(const LLUUID& asset_id, const LLUUID& agent_id, const std::string& host_name)
{
	if (agent_id.isNull() || asset_id.isNull())
	{
		llwarns << "TAT: addTempAssetData bad id's asset_id: " << asset_id << "  agent_id: " << agent_id << llendl;
		return;
	}

	LLTempAssetData temp_asset_data;
	temp_asset_data.mAssetID = asset_id;
	temp_asset_data.mAgentID = agent_id;
	temp_asset_data.mHostName = host_name;

	mTempAssets[asset_id] = temp_asset_data;
}

// virtual
BOOL LLHTTPAssetStorage::hasTempAssetData(const LLUUID& texture_id) const
{
	uuid_tempdata_map::const_iterator citer = mTempAssets.find(texture_id);
	BOOL found = (citer != mTempAssets.end());
	return found;
}

// virtual
std::string LLHTTPAssetStorage::getTempAssetHostName(const LLUUID& texture_id) const
{
	uuid_tempdata_map::const_iterator citer = mTempAssets.find(texture_id);
	if (citer != mTempAssets.end())
	{
		return citer->second.mHostName;
	}
	else
	{
		return std::string();
	}
}

// virtual 
LLUUID LLHTTPAssetStorage::getTempAssetAgentID(const LLUUID& texture_id) const
{
	uuid_tempdata_map::const_iterator citer = mTempAssets.find(texture_id);
	if (citer != mTempAssets.end())
	{
		return citer->second.mAgentID;
	}
	else
	{
		return LLUUID::null;
	}
}

// virtual 
void LLHTTPAssetStorage::removeTempAssetData(const LLUUID& asset_id)
{
	mTempAssets.erase(asset_id);
}

// virtual 
void LLHTTPAssetStorage::removeTempAssetDataByAgentID(const LLUUID& agent_id)
{
	uuid_tempdata_map::iterator it = mTempAssets.begin();
	uuid_tempdata_map::iterator end = mTempAssets.end();

	while (it != end)
	{
		const LLTempAssetData& asset_data = it->second;
		if (asset_data.mAgentID == agent_id)
		{
			mTempAssets.erase(it++);
		}
		else
		{
			++it;
		}
	}
}

std::string LLHTTPAssetStorage::getBaseURL(const LLUUID& asset_id, LLAssetType::EType asset_type)
{
	if (LLAssetType::AT_TEXTURE == asset_type)
	{
		uuid_tempdata_map::const_iterator citer = mTempAssets.find(asset_id);
		if (citer != mTempAssets.end())
		{
			const std::string& host_name = citer->second.mHostName;
			std::string url = llformat(LOCAL_ASSET_URL_FORMAT, host_name.c_str());
			return url;
		}
	}

	return mBaseURL;
}

void LLHTTPAssetStorage::dumpTempAssetData(const LLUUID& avatar_id) const
{
	uuid_tempdata_map::const_iterator it = mTempAssets.begin();
	uuid_tempdata_map::const_iterator end = mTempAssets.end();
	S32 count = 0;
	for ( ; it != end; ++it)
	{
		const LLTempAssetData& temp_asset_data = it->second;
		if (avatar_id.isNull()
			|| avatar_id == temp_asset_data.mAgentID)
		{
			llinfos << "TAT: dump agent " << temp_asset_data.mAgentID
				<< " texture " << temp_asset_data.mAssetID
				<< " host " << temp_asset_data.mHostName
				<< llendl;
			count++;
		}
	}

	if (avatar_id.isNull())
	{
		llinfos << "TAT: dumped " << count << " entries for all avatars" << llendl;
	}
	else
	{
		llinfos << "TAT: dumped " << count << " entries for avatar " << avatar_id << llendl;
	}
}

void LLHTTPAssetStorage::clearTempAssetData()
{
	llinfos << "TAT: Clearing temp asset data map" << llendl;
	mTempAssets.clear();
}

