diff options
Diffstat (limited to 'indra/viewer_components/updater/llupdatedownloader.cpp')
-rw-r--r-- | indra/viewer_components/updater/llupdatedownloader.cpp | 604 |
1 files changed, 0 insertions, 604 deletions
diff --git a/indra/viewer_components/updater/llupdatedownloader.cpp b/indra/viewer_components/updater/llupdatedownloader.cpp deleted file mode 100644 index 382689afa0..0000000000 --- a/indra/viewer_components/updater/llupdatedownloader.cpp +++ /dev/null @@ -1,604 +0,0 @@ -/** - * @file llupdatedownloader.cpp - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include "llupdatedownloader.h" -#include "httpcommon.h" -#include <stdexcept> -#include <boost/format.hpp> -#include <boost/lexical_cast.hpp> -#include <curl/curl.h> -#include "lldir.h" -#include "llevents.h" -#include "llfile.h" -#include "llmd5.h" -#include "llsd.h" -#include "llsdserialize.h" -#include "llthread.h" -#include "llupdaterservice.h" - -class LLUpdateDownloader::Implementation: - public LLThread -{ -public: - Implementation(LLUpdateDownloader::Client & client); - ~Implementation(); - void cancel(void); - void download(LLURI const & uri, - std::string const & hash, - std::string const & updateChannel, - std::string const & updateVersion, - std::string const & info_url, - bool required); - bool isDownloading(void); - size_t onHeader(void * header, size_t size); - size_t onBody(void * header, size_t size); - int onProgress(curl_off_t downloadSize, curl_off_t bytesDownloaded); - void resume(void); - void setBandwidthLimit(U64 bytesPerSecond); - -private: - curl_off_t mBandwidthLimit; - bool mCancelled; - LLUpdateDownloader::Client & mClient; - LLCore::LLHttp::CURL_ptr mCurl; - LLSD mDownloadData; - llofstream mDownloadStream; - unsigned char mDownloadPercent; - std::string mDownloadRecordPath; - curl_slist * mHeaderList; - - void initializeCurlGet(std::string const & url, bool processHeader); - void resumeDownloading(size_t startByte); - void run(void); - void startDownloading(LLURI const & uri, std::string const & hash); - void throwOnCurlError(CURLcode code); - bool validateDownload(const std::string& filePath); - bool validateOrRemove(const std::string& filePath); - - LOG_CLASS(LLUpdateDownloader::Implementation); -}; - - -namespace { - class DownloadError: - public std::runtime_error - { - public: - DownloadError(const char * message): - std::runtime_error(message) - { - ; // No op. - } - }; - - - const char * gSecondLifeUpdateRecord = "SecondLifeUpdateDownload.xml"; -}; - - - -// LLUpdateDownloader -//----------------------------------------------------------------------------- - - - -std::string LLUpdateDownloader::downloadMarkerPath(void) -{ - return gDirUtilp->getExpandedFilename(LL_PATH_LOGS, gSecondLifeUpdateRecord); -} - - -LLUpdateDownloader::LLUpdateDownloader(Client & client): - mImplementation(new LLUpdateDownloader::Implementation(client)) -{ - ; // No op. -} - - -void LLUpdateDownloader::cancel(void) -{ - mImplementation->cancel(); -} - - -void LLUpdateDownloader::download(LLURI const & uri, - std::string const & hash, - std::string const & updateChannel, - std::string const & updateVersion, - std::string const & info_url, - bool required) -{ - mImplementation->download(uri, hash, updateChannel, updateVersion, info_url, required); -} - - -bool LLUpdateDownloader::isDownloading(void) -{ - return mImplementation->isDownloading(); -} - - -void LLUpdateDownloader::resume(void) -{ - mImplementation->resume(); -} - - -void LLUpdateDownloader::setBandwidthLimit(U64 bytesPerSecond) -{ - mImplementation->setBandwidthLimit(bytesPerSecond); -} - - - -// LLUpdateDownloader::Implementation -//----------------------------------------------------------------------------- - - -namespace { - size_t write_function(void * data, size_t blockSize, size_t blocks, void * downloader) - { - size_t bytes = blockSize * blocks; - return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)->onBody(data, bytes); - } - - - size_t header_function(void * data, size_t blockSize, size_t blocks, void * downloader) - { - size_t bytes = blockSize * blocks; - return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)->onHeader(data, bytes); - } - - - int xferinfo_callback(void * downloader, - curl_off_t dowloadTotal, - curl_off_t downloadNow, - curl_off_t uploadTotal, - curl_off_t uploadNow) - { - return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)-> - onProgress(dowloadTotal, downloadNow); - } -} - - -LLUpdateDownloader::Implementation::Implementation(LLUpdateDownloader::Client & client): - LLThread("LLUpdateDownloader"), - mBandwidthLimit(0), - mCancelled(false), - mClient(client), - mCurl(), - mDownloadPercent(0), - mHeaderList(0) -{ - CURLcode code = curl_global_init(CURL_GLOBAL_ALL); // Just in case. - llverify(code == CURLE_OK); // TODO: real error handling here. -} - - -LLUpdateDownloader::Implementation::~Implementation() -{ - if(isDownloading()) - { - cancel(); - shutdown(); - } - else - { - ; // No op. - } - mCurl.reset(); -} - - -void LLUpdateDownloader::Implementation::cancel(void) -{ - mCancelled = true; -} - - -void LLUpdateDownloader::Implementation::download(LLURI const & uri, - std::string const & hash, - std::string const & updateChannel, - std::string const & updateVersion, - std::string const & info_url, - bool required) -{ - if(isDownloading()) mClient.downloadError("download in progress"); - - mDownloadRecordPath = downloadMarkerPath(); - mDownloadData = LLSD(); - mDownloadData["required"] = required; - mDownloadData["update_channel"] = updateChannel; - mDownloadData["update_version"] = updateVersion; - if (!info_url.empty()) - { - mDownloadData["info_url"] = info_url; - } - try - { - startDownloading(uri, hash); - } - catch(DownloadError const & e) - { - mClient.downloadError(e.what()); - } -} - - -bool LLUpdateDownloader::Implementation::isDownloading(void) -{ - return !isStopped(); -} - - -void LLUpdateDownloader::Implementation::resume(void) -{ - mCancelled = false; - - if(isDownloading()) - { - mClient.downloadError("download in progress"); - } - - mDownloadRecordPath = downloadMarkerPath(); - llifstream dataStream(mDownloadRecordPath.c_str()); - if(!dataStream) - { - mClient.downloadError("no download marker"); - return; - } - - LLSDSerialize::fromXMLDocument(mDownloadData, dataStream); - - if(!mDownloadData.asBoolean()) - { - mClient.downloadError("no download information in marker"); - return; - } - - std::string filePath = mDownloadData["path"].asString(); - try - { - if(LLFile::isfile(filePath)) - { - llstat fileStatus; - LLFile::stat(filePath, &fileStatus); - if(fileStatus.st_size != mDownloadData["size"].asInteger()) - { - resumeDownloading(fileStatus.st_size); - } - else if(!validateOrRemove(filePath)) - { - download(LLURI(mDownloadData["url"].asString()), - mDownloadData["hash"].asString(), - mDownloadData["update_channel"].asString(), - mDownloadData["update_version"].asString(), - mDownloadData["info_url"].asString(), - mDownloadData["required"].asBoolean()); - } - else - { - mClient.downloadComplete(mDownloadData); - } - } - else - { - download(LLURI(mDownloadData["url"].asString()), - mDownloadData["hash"].asString(), - mDownloadData["update_channel"].asString(), - mDownloadData["update_version"].asString(), - mDownloadData["info_url"].asString(), - mDownloadData["required"].asBoolean()); - } - } - catch(DownloadError & e) - { - mClient.downloadError(e.what()); - } -} - - -void LLUpdateDownloader::Implementation::setBandwidthLimit(U64 bytesPerSecond) -{ - if((mBandwidthLimit != bytesPerSecond) && isDownloading() && !mDownloadData["required"].asBoolean()) - { - llassert(static_cast<bool>(mCurl)); - mBandwidthLimit = bytesPerSecond; - CURLcode code = curl_easy_setopt(mCurl.get(), CURLOPT_MAX_RECV_SPEED_LARGE, &mBandwidthLimit); - if(code != CURLE_OK) - { - LL_WARNS("UpdaterService") << "unable to change dowload bandwidth" << LL_ENDL; - } - } - else - { - mBandwidthLimit = bytesPerSecond; - } -} - - -size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size) -{ - char const * headerPtr = reinterpret_cast<const char *> (buffer); - std::string header(headerPtr, headerPtr + size); - size_t colonPosition = header.find(':'); - if(colonPosition == std::string::npos) return size; // HTML response; ignore. - - if(header.substr(0, colonPosition) == "Content-Length") { - try { - size_t firstDigitPos = header.find_first_of("0123456789", colonPosition); - size_t lastDigitPos = header.find_last_of("0123456789"); - std::string contentLength = header.substr(firstDigitPos, lastDigitPos - firstDigitPos + 1); - size_t size = boost::lexical_cast<size_t>(contentLength); - LL_INFOS("UpdaterService") << "download size is " << size << LL_ENDL; - - mDownloadData["size"] = LLSD(LLSD::Integer(size)); - llofstream odataStream(mDownloadRecordPath.c_str()); - LLSDSerialize::toPrettyXML(mDownloadData, odataStream); - } catch (std::exception const & e) { - LL_WARNS("UpdaterService") << "unable to read content length (" - << e.what() << ")" << LL_ENDL; - } - } else { - ; // No op. - } - - return size; -} - - -size_t LLUpdateDownloader::Implementation::onBody(void * buffer, size_t size) -{ - if(mCancelled) return 0; // Forces a write error which will halt curl thread. - if((size == 0) || (buffer == 0)) return 0; - - mDownloadStream.write(static_cast<const char *>(buffer), size); - if(mDownloadStream.bad()) { - return 0; - } else { - return size; - } -} - - -int LLUpdateDownloader::Implementation::onProgress(curl_off_t downloadSize, curl_off_t bytesDownloaded) -{ - int downloadPercent = static_cast<int>(100.0 * ((double) bytesDownloaded / (double) downloadSize)); - if(downloadPercent > mDownloadPercent) { - mDownloadPercent = downloadPercent; - - LLSD event; - event["pump"] = LLUpdaterService::pumpName(); - LLSD payload; - payload["type"] = LLSD(LLUpdaterService::PROGRESS); - payload["download_size"] = (LLSD::Integer) downloadSize; - payload["bytes_downloaded"] = (LLSD::Integer) bytesDownloaded; - event["payload"] = payload; - LLEventPumps::instance().obtain("mainlooprepeater").post(event); - - LL_INFOS("UpdaterService") << "progress event " << payload << LL_ENDL; - } else { - ; // Keep events to a reasonalbe number. - } - - return 0; -} - - -void LLUpdateDownloader::Implementation::run(void) -{ - CURLcode code = curl_easy_perform(mCurl.get()); - mDownloadStream.close(); - if(code == CURLE_OK) - { - LLFile::remove(mDownloadRecordPath); - if(validateOrRemove(mDownloadData["path"])) - { - LL_INFOS("UpdaterService") << "download successful" << LL_ENDL; - mClient.downloadComplete(mDownloadData); - } - else - { - mClient.downloadError("failed hash check"); - } - } - else if(mCancelled && (code == CURLE_WRITE_ERROR)) - { - LL_INFOS("UpdaterService") << "download canceled by user" << LL_ENDL; - // Do not call back client. - } - else - { - LL_WARNS("UpdaterService") << "download failed with error '" << - curl_easy_strerror(code) << "'" << LL_ENDL; - LLFile::remove(mDownloadRecordPath); - if(mDownloadData.has("path")) - { - std::string filePath = mDownloadData["path"].asString(); - LL_INFOS("UpdaterService") << "removing " << filePath << LL_ENDL; - LLFile::remove(filePath); - } - mClient.downloadError("curl error"); - } - - if(mHeaderList) - { - curl_slist_free_all(mHeaderList); - mHeaderList = 0; - } -} - - -void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & url, bool processHeader) -{ - if(!mCurl) - { - mCurl = LLCore::LLHttp::createEasyHandle(); - } - else - { - curl_easy_reset(mCurl.get()); - } - - if(!mCurl) - { - throw DownloadError("failed to initialize curl"); - } - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_NOSIGNAL, true)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_FOLLOWLOCATION, true)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_WRITEFUNCTION, &write_function)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_WRITEDATA, this)); - if(processHeader) - { - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_HEADERFUNCTION, &header_function)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_HEADERDATA, this)); - } - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_HTTPGET, true)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_URL, url.c_str())); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_XFERINFOFUNCTION, &xferinfo_callback)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_XFERINFODATA, this)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_NOPROGRESS, 0)); - // if it's a required update set the bandwidth limit to 0 (unlimited) - curl_off_t limit = mDownloadData["required"].asBoolean() ? 0 : mBandwidthLimit; - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_MAX_RECV_SPEED_LARGE, limit)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_CAINFO, gDirUtilp->getCAFile().c_str())); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_SSL_VERIFYHOST, 2)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_SSL_VERIFYPEER, 1)); - - mDownloadPercent = 0; -} - - -void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte) -{ - LL_INFOS("UpdaterService") << "resuming download from " << mDownloadData["url"].asString() - << " at byte " << startByte << LL_ENDL; - - initializeCurlGet(mDownloadData["url"].asString(), false); - - // The header 'Range: bytes n-' will request the bytes remaining in the - // source begining with byte n and ending with the last byte. - boost::format rangeHeaderFormat("Range: bytes=%u-"); - rangeHeaderFormat % startByte; - mHeaderList = curl_slist_append(mHeaderList, rangeHeaderFormat.str().c_str()); - if(mHeaderList == 0) - { - throw DownloadError("cannot add Range header"); - } - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_HTTPHEADER, mHeaderList)); - - mDownloadStream.open(mDownloadData["path"].asString().c_str(), - std::ios_base::out | std::ios_base::binary | std::ios_base::app); - start(); -} - - -void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri, std::string const & hash) -{ - mDownloadData["url"] = uri.asString(); - mDownloadData["hash"] = hash; - mDownloadData["current_version"] = ll_get_version(); - LLSD path = uri.pathArray(); - if(path.size() == 0) throw DownloadError("no file path"); - std::string fileName = path[path.size() - 1].asString(); - std::string filePath = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, fileName); - mDownloadData["path"] = filePath; - - LL_INFOS("UpdaterService") << "downloading " << filePath - << " from " << uri.asString() << LL_ENDL; - LL_INFOS("UpdaterService") << "hash of file is " << hash << LL_ENDL; - - llofstream dataStream(mDownloadRecordPath.c_str()); - LLSDSerialize::toPrettyXML(mDownloadData, dataStream); - - mDownloadStream.open(filePath.c_str(), std::ios_base::out | std::ios_base::binary); - initializeCurlGet(uri.asString(), true); - start(); -} - - -void LLUpdateDownloader::Implementation::throwOnCurlError(CURLcode code) -{ - if(code != CURLE_OK) { - const char * errorString = curl_easy_strerror(code); - if(errorString != 0) { - throw DownloadError(curl_easy_strerror(code)); - } else { - throw DownloadError("unknown curl error"); - } - } else { - ; // No op. - } -} - -bool LLUpdateDownloader::Implementation::validateOrRemove(const std::string& filePath) -{ - bool valid = validateDownload(filePath); - if (! valid) - { - LL_INFOS("UpdaterService") << "removing " << filePath << LL_ENDL; - LLFile::remove(filePath); - } - return valid; -} - -bool LLUpdateDownloader::Implementation::validateDownload(const std::string& filePath) -{ - llifstream fileStream(filePath.c_str(), std::ios_base::in | std::ios_base::binary); - if(!fileStream) - { - LL_INFOS("UpdaterService") << "can't open " << filePath << ", invalid" << LL_ENDL; - return false; - } - - std::string hash = mDownloadData["hash"].asString(); - if (! hash.empty()) - { - char digest[33]; - LLMD5(fileStream).hex_digest(digest); - if (hash == digest) - { - LL_INFOS("UpdaterService") << "verified hash " << hash - << " for downloaded " << filePath << LL_ENDL; - return true; - } - else - { - LL_WARNS("UpdaterService") << "download hash mismatch for " - << filePath << ": expected " << hash - << " but computed " << digest << LL_ENDL; - return false; - } - } - else - { - LL_INFOS("UpdaterService") << "no hash specified for " << filePath - << ", unverified" << LL_ENDL; - return true; // No hash check provided. - } -} |