diff options
Diffstat (limited to 'indra/llmessage/llhttpassetstorage.cpp')
-rw-r--r-- | indra/llmessage/llhttpassetstorage.cpp | 996 |
1 files changed, 996 insertions, 0 deletions
diff --git a/indra/llmessage/llhttpassetstorage.cpp b/indra/llmessage/llhttpassetstorage.cpp new file mode 100644 index 0000000000..12d9d610cc --- /dev/null +++ b/indra/llmessage/llhttpassetstorage.cpp @@ -0,0 +1,996 @@ +/** + * @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(); +} + |