summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/viewer_components/updater/llupdatechecker.cpp35
-rw-r--r--indra/viewer_components/updater/llupdatechecker.h11
-rw-r--r--indra/viewer_components/updater/llupdatedownloader.cpp251
-rw-r--r--indra/viewer_components/updater/llupdatedownloader.h16
-rw-r--r--indra/viewer_components/updater/llupdaterservice.cpp22
-rw-r--r--indra/viewer_components/updater/tests/llupdaterservice_test.cpp2
6 files changed, 241 insertions, 96 deletions
diff --git a/indra/viewer_components/updater/llupdatechecker.cpp b/indra/viewer_components/updater/llupdatechecker.cpp
index 2c60636122..d31244cc9b 100644
--- a/indra/viewer_components/updater/llupdatechecker.cpp
+++ b/indra/viewer_components/updater/llupdatechecker.cpp
@@ -24,21 +24,35 @@
*/
#include "linden_common.h"
+#include <stdexcept>
#include <boost/format.hpp>
#include "llhttpclient.h"
#include "llsd.h"
#include "llupdatechecker.h"
#include "lluri.h"
+
#if LL_WINDOWS
#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
#endif
+
+class LLUpdateChecker::CheckError:
+ public std::runtime_error
+{
+public:
+ CheckError(const char * message):
+ std::runtime_error(message)
+ {
+ ; // No op.
+ }
+};
+
+
class LLUpdateChecker::Implementation:
public LLHTTPClient::Responder
{
public:
-
Implementation(Client & client);
~Implementation();
void check(std::string const & protocolVersion, std::string const & hostUrl,
@@ -50,9 +64,8 @@ public:
const LLSD& content);
virtual void error(U32 status, const std::string & reason);
-private:
- std::string buildUrl(std::string const & protocolVersion, std::string const & hostUrl,
- std::string const & servicePath, std::string channel, std::string version);
+private:
+ static const char * sProtocolVersion;
Client & mClient;
LLHTTPClient mHttpClient;
@@ -60,6 +73,9 @@ private:
LLHTTPClient::ResponderPtr mMe;
std::string mVersion;
+ std::string buildUrl(std::string const & protocolVersion, std::string const & hostUrl,
+ std::string const & servicePath, std::string channel, std::string version);
+
LOG_CLASS(LLUpdateChecker::Implementation);
};
@@ -88,6 +104,9 @@ void LLUpdateChecker::check(std::string const & protocolVersion, std::string con
//-----------------------------------------------------------------------------
+const char * LLUpdateChecker::Implementation::sProtocolVersion = "v1.0";
+
+
LLUpdateChecker::Implementation::Implementation(LLUpdateChecker::Client & client):
mClient(client),
mInProgress(false),
@@ -106,7 +125,9 @@ LLUpdateChecker::Implementation::~Implementation()
void LLUpdateChecker::Implementation::check(std::string const & protocolVersion, std::string const & hostUrl,
std::string const & servicePath, std::string channel, std::string version)
{
- // llassert(!mInProgress);
+ llassert(!mInProgress);
+
+ if(protocolVersion != sProtocolVersion) throw CheckError("unsupported protocol");
mInProgress = true;
mVersion = version;
@@ -135,11 +156,11 @@ void LLUpdateChecker::Implementation::completed(U32 status,
} else if(content["required"].asBoolean()) {
LL_INFOS("UpdateCheck") << "version invalid" << llendl;
LLURI uri(content["url"].asString());
- mClient.requiredUpdate(content["version"].asString(), uri);
+ mClient.requiredUpdate(content["version"].asString(), uri, content["hash"].asString());
} else {
LL_INFOS("UpdateCheck") << "newer version " << content["version"].asString() << " available" << llendl;
LLURI uri(content["url"].asString());
- mClient.optionalUpdate(content["version"].asString(), uri);
+ mClient.optionalUpdate(content["version"].asString(), uri, content["hash"].asString());
}
}
diff --git a/indra/viewer_components/updater/llupdatechecker.h b/indra/viewer_components/updater/llupdatechecker.h
index 58aaee4e3d..cea1f13647 100644
--- a/indra/viewer_components/updater/llupdatechecker.h
+++ b/indra/viewer_components/updater/llupdatechecker.h
@@ -38,6 +38,9 @@ public:
class Client;
class Implementation;
+ // An exception that may be raised on check errors.
+ class CheckError;
+
LLUpdateChecker(Client & client);
// Check status of current app on the given host for the channel and version provided.
@@ -62,10 +65,14 @@ public:
virtual void error(std::string const & message) = 0;
// A newer version is available, but the current version may still be used.
- virtual void optionalUpdate(std::string const & newVersion, LLURI const & uri) = 0;
+ virtual void optionalUpdate(std::string const & newVersion,
+ LLURI const & uri,
+ std::string const & hash) = 0;
// A newer version is available, and the current version is no longer valid.
- virtual void requiredUpdate(std::string const & newVersion, LLURI const & uri) = 0;
+ virtual void requiredUpdate(std::string const & newVersion,
+ LLURI const & uri,
+ std::string const & hash) = 0;
// The checked version is up to date; no newer version exists.
virtual void upToDate(void) = 0;
diff --git a/indra/viewer_components/updater/llupdatedownloader.cpp b/indra/viewer_components/updater/llupdatedownloader.cpp
index 087d79f804..102f2f9eec 100644
--- a/indra/viewer_components/updater/llupdatedownloader.cpp
+++ b/indra/viewer_components/updater/llupdatedownloader.cpp
@@ -24,10 +24,13 @@
*/
#include "linden_common.h"
+#include <stdexcept>
+#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#include <curl/curl.h>
#include "lldir.h"
#include "llfile.h"
+#include "llmd5.h"
#include "llsd.h"
#include "llsdserialize.h"
#include "llthread.h"
@@ -41,33 +44,59 @@ public:
Implementation(LLUpdateDownloader::Client & client);
~Implementation();
void cancel(void);
- void download(LLURI const & uri);
+ void download(LLURI const & uri, std::string const & hash);
bool isDownloading(void);
void onHeader(void * header, size_t size);
void onBody(void * header, size_t size);
-private:
- static const char * sSecondLifeUpdateRecord;
+ void resume(void);
+private:
LLUpdateDownloader::Client & mClient;
CURL * mCurl;
+ LLSD mDownloadData;
llofstream mDownloadStream;
std::string mDownloadRecordPath;
- void initializeCurlGet(std::string const & url);
- void resumeDownloading(LLSD const & downloadData);
+ void initializeCurlGet(std::string const & url, bool processHeader);
+ void resumeDownloading(size_t startByte);
void run(void);
- bool shouldResumeOngoingDownload(LLURI const & uri, LLSD & downloadData);
- void startDownloading(LLURI const & uri);
+ void startDownloading(LLURI const & uri, std::string const & hash);
+ void throwOnCurlError(CURLcode code);
+ bool validateDownload(void);
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))
{
@@ -81,9 +110,9 @@ void LLUpdateDownloader::cancel(void)
}
-void LLUpdateDownloader::download(LLURI const & uri)
+void LLUpdateDownloader::download(LLURI const & uri, std::string const & hash)
{
- mImplementation->download(uri);
+ mImplementation->download(uri, hash);
}
@@ -93,6 +122,12 @@ bool LLUpdateDownloader::isDownloading(void)
}
+void LLUpdateDownloader::resume(void)
+{
+ mImplementation->resume();
+}
+
+
// LLUpdateDownloader::Implementation
//-----------------------------------------------------------------------------
@@ -106,6 +141,7 @@ namespace {
return bytes;
}
+
size_t header_function(void * data, size_t blockSize, size_t blocks, void * downloader)
{
size_t bytes = blockSize * blocks;
@@ -115,15 +151,11 @@ namespace {
}
-const char * LLUpdateDownloader::Implementation::sSecondLifeUpdateRecord =
- "SecondLifeUpdateDownload.xml";
-
-
LLUpdateDownloader::Implementation::Implementation(LLUpdateDownloader::Client & client):
LLThread("LLUpdateDownloader"),
mClient(client),
mCurl(0),
- mDownloadRecordPath(gDirUtilp->getExpandedFilename(LL_PATH_LOGS, sSecondLifeUpdateRecord))
+ mDownloadRecordPath(LLUpdateDownloader::downloadMarkerPath())
{
CURLcode code = curl_global_init(CURL_GLOBAL_ALL); // Just in case.
llassert(code = CURLE_OK); // TODO: real error handling here.
@@ -142,13 +174,15 @@ void LLUpdateDownloader::Implementation::cancel(void)
}
-void LLUpdateDownloader::Implementation::download(LLURI const & uri)
+void LLUpdateDownloader::Implementation::download(LLURI const & uri, std::string const & hash)
{
- LLSD downloadData;
- if(shouldResumeOngoingDownload(uri, downloadData)){
- startDownloading(uri); // TODO: Implement resume.
- } else {
- startDownloading(uri);
+ if(isDownloading()) mClient.downloadError("download in progress");
+
+ mDownloadData = LLSD();
+ try {
+ startDownloading(uri, hash);
+ } catch(DownloadError const & e) {
+ mClient.downloadError(e.what());
}
}
@@ -158,6 +192,45 @@ bool LLUpdateDownloader::Implementation::isDownloading(void)
return !isStopped();
}
+
+void LLUpdateDownloader::Implementation::resume(void)
+{
+ llifstream dataStream(mDownloadRecordPath);
+ if(!dataStream) {
+ mClient.downloadError("no download marker");
+ return;
+ }
+
+ LLSDSerialize parser;
+ parser.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(!validateDownload()) {
+ LLFile::remove(filePath);
+ download(LLURI(mDownloadData["url"].asString()), mDownloadData["hash"].asString());
+ } else {
+ mClient.downloadComplete(mDownloadData);
+ }
+ } else {
+ download(LLURI(mDownloadData["url"].asString()), mDownloadData["hash"].asString());
+ }
+ } catch(DownloadError & e) {
+ mClient.downloadError(e.what());
+ }
+}
+
+
void LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size)
{
char const * headerPtr = reinterpret_cast<const char *> (buffer);
@@ -173,14 +246,10 @@ void LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size)
size_t size = boost::lexical_cast<size_t>(contentLength);
LL_INFOS("UpdateDownload") << "download size is " << size << LL_ENDL;
- LLSD downloadData;
- llifstream idataStream(mDownloadRecordPath);
- LLSDSerialize parser;
- parser.fromXMLDocument(downloadData, idataStream);
- idataStream.close();
- downloadData["size"] = LLSD(LLSD::Integer(size));
+ mDownloadData["size"] = LLSD(LLSD::Integer(size));
llofstream odataStream(mDownloadRecordPath);
- parser.toPrettyXML(downloadData, odataStream);
+ LLSDSerialize parser;
+ parser.toPrettyXML(mDownloadData, odataStream);
} catch (std::exception const & e) {
LL_WARNS("UpdateDownload") << "unable to read content length ("
<< e.what() << ")" << LL_ENDL;
@@ -200,17 +269,26 @@ void LLUpdateDownloader::Implementation::onBody(void * buffer, size_t size)
void LLUpdateDownloader::Implementation::run(void)
{
CURLcode code = curl_easy_perform(mCurl);
+ LLFile::remove(mDownloadRecordPath);
if(code == CURLE_OK) {
- LL_INFOS("UpdateDownload") << "download successful" << LL_ENDL;
- mClient.downloadComplete();
+ if(validateDownload()) {
+ LL_INFOS("UpdateDownload") << "download successful" << LL_ENDL;
+ mClient.downloadComplete(mDownloadData);
+ } else {
+ LL_INFOS("UpdateDownload") << "download failed hash check" << LL_ENDL;
+ std::string filePath = mDownloadData["path"].asString();
+ if(filePath.size() != 0) LLFile::remove(filePath);
+ mClient.downloadError("failed hash check");
+ }
} else {
- LL_WARNS("UpdateDownload") << "download failed with error " << code << LL_ENDL;
+ LL_WARNS("UpdateDownload") << "download failed with error '" <<
+ curl_easy_strerror(code) << "'" << LL_ENDL;
mClient.downloadError("curl error");
}
}
-void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & url)
+void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & url, bool processHeader)
{
if(mCurl == 0) {
mCurl = curl_easy_init();
@@ -218,64 +296,93 @@ void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & u
curl_easy_reset(mCurl);
}
- llassert(mCurl != 0); // TODO: real error handling here.
+ if(mCurl == 0) throw DownloadError("failed to initialize curl");
- CURLcode code;
- code = curl_easy_setopt(mCurl, CURLOPT_NOSIGNAL, true);
- code = curl_easy_setopt(mCurl, CURLOPT_FOLLOWLOCATION, true);
- code = curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, &write_function);
- code = curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, this);
- code = curl_easy_setopt(mCurl, CURLOPT_HEADERFUNCTION, &header_function);
- code = curl_easy_setopt(mCurl, CURLOPT_HEADERDATA, this);
- code = curl_easy_setopt(mCurl, CURLOPT_HTTPGET, true);
- code = curl_easy_setopt(mCurl, CURLOPT_URL, url.c_str());
-}
-
-
-void LLUpdateDownloader::Implementation::resumeDownloading(LLSD const & downloadData)
-{
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOSIGNAL, true));
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_FOLLOWLOCATION, true));
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, &write_function));
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, this));
+ if(processHeader) {
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERFUNCTION, &header_function));
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERDATA, this));
+ }
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPGET, true));
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_URL, url.c_str()));
}
-bool LLUpdateDownloader::Implementation::shouldResumeOngoingDownload(LLURI const & uri, LLSD & downloadData)
+void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte)
{
- if(!LLFile::isfile(mDownloadRecordPath)) return false;
-
- llifstream dataStream(mDownloadRecordPath);
- LLSDSerialize parser;
- parser.fromXMLDocument(downloadData, dataStream);
+ initializeCurlGet(mDownloadData["url"].asString(), false);
- if(downloadData["url"].asString() != uri.asString()) return 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;
+ curl_slist * headerList = 0;
+ headerList = curl_slist_append(headerList, rangeHeaderFormat.str().c_str());
+ if(headerList == 0) throw DownloadError("cannot add Range header");
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, headerList));
+ curl_slist_free_all(headerList);
- std::string downloadedFilePath = downloadData["path"].asString();
- if(LLFile::isfile(downloadedFilePath)) {
- llstat fileStatus;
- LLFile::stat(downloadedFilePath, &fileStatus);
- downloadData["bytes_downloaded"] = LLSD(LLSD::Integer(fileStatus.st_size));
- return true;
- } else {
- return false;
- }
-
- return true;
+ mDownloadStream.open(mDownloadData["path"].asString(),
+ std::ios_base::out | std::ios_base::binary | std::ios_base::app);
+ start();
}
-void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri)
+void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri, std::string const & hash)
{
- LLSD downloadData;
- downloadData["url"] = uri.asString();
+ mDownloadData["url"] = uri.asString();
+ mDownloadData["hash"] = hash;
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);
- LL_INFOS("UpdateDownload") << "downloading " << filePath << LL_ENDL;
- LL_INFOS("UpdateDownload") << "from " << uri.asString() << LL_ENDL;
- downloadData["path"] = filePath;
+ mDownloadData["path"] = filePath;
+
+ LL_INFOS("UpdateDownload") << "downloading " << filePath
+ << " from " << uri.asString() << LL_ENDL;
+ LL_INFOS("UpdateDownload") << "hash of file is " << hash << LL_ENDL;
+
llofstream dataStream(mDownloadRecordPath);
LLSDSerialize parser;
- parser.toPrettyXML(downloadData, dataStream);
+ parser.toPrettyXML(mDownloadData, dataStream);
mDownloadStream.open(filePath, std::ios_base::out | std::ios_base::binary);
- initializeCurlGet(uri.asString());
+ 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::validateDownload(void)
+{
+ std::string filePath = mDownloadData["path"].asString();
+ llifstream fileStream(filePath);
+ if(!fileStream) return false;
+
+ std::string hash = mDownloadData["hash"].asString();
+ if(hash.size() != 0) {
+ LL_INFOS("UpdateDownload") << "checking hash..." << LL_ENDL;
+ char digest[33];
+ LLMD5(fileStream).hex_digest(digest);
+ return hash == digest;
+ } else {
+ return true; // No hash check provided.
+ }
+}
diff --git a/indra/viewer_components/updater/llupdatedownloader.h b/indra/viewer_components/updater/llupdatedownloader.h
index 395d19d6bf..dc8ecc378a 100644
--- a/indra/viewer_components/updater/llupdatedownloader.h
+++ b/indra/viewer_components/updater/llupdatedownloader.h
@@ -27,7 +27,6 @@
#define LL_UPDATE_DOWNLOADER_H
-#include <stdexcept>
#include <string>
#include <boost/shared_ptr.hpp>
#include "lluri.h"
@@ -39,24 +38,27 @@
class LLUpdateDownloader
{
public:
- class BusyError;
class Client;
class Implementation;
+ // Returns the path to the download marker file containing details of the
+ // latest download.
+ static std::string downloadMarkerPath(void);
+
LLUpdateDownloader(Client & client);
// Cancel any in progress download; a no op if none is in progress.
void cancel(void);
// Start a new download.
- //
- // This method will throw a BusyException instance if a download is already
- // in progress.
- void download(LLURI const & uri);
+ void download(LLURI const & uri, std::string const & hash);
// Returns true if a download is in progress.
bool isDownloading(void);
+ // Resume a partial download.
+ void resume(void);
+
private:
boost::shared_ptr<Implementation> mImplementation;
};
@@ -69,7 +71,7 @@ class LLUpdateDownloader::Client {
public:
// The download has completed successfully.
- virtual void downloadComplete(void) = 0;
+ virtual void downloadComplete(LLSD const & data) = 0;
// The download failed.
virtual void downloadError(std::string const & message) = 0;
diff --git a/indra/viewer_components/updater/llupdaterservice.cpp b/indra/viewer_components/updater/llupdaterservice.cpp
index e865552fb3..dc48606cbc 100644
--- a/indra/viewer_components/updater/llupdaterservice.cpp
+++ b/indra/viewer_components/updater/llupdaterservice.cpp
@@ -90,12 +90,16 @@ public:
// LLUpdateChecker::Client:
virtual void error(std::string const & message);
- virtual void optionalUpdate(std::string const & newVersion, LLURI const & uri);
- virtual void requiredUpdate(std::string const & newVersion, LLURI const & uri);
+ virtual void optionalUpdate(std::string const & newVersion,
+ LLURI const & uri,
+ std::string const & hash);
+ virtual void requiredUpdate(std::string const & newVersion,
+ LLURI const & uri,
+ std::string const & hash);
virtual void upToDate(void);
// LLUpdateDownloader::Client
- void downloadComplete(void) { retry(); }
+ void downloadComplete(LLSD const & data) { retry(); }
void downloadError(std::string const & message) { retry(); }
bool onMainLoop(LLSD const & event);
@@ -195,14 +199,18 @@ void LLUpdaterServiceImpl::error(std::string const & message)
retry();
}
-void LLUpdaterServiceImpl::optionalUpdate(std::string const & newVersion, LLURI const & uri)
+void LLUpdaterServiceImpl::optionalUpdate(std::string const & newVersion,
+ LLURI const & uri,
+ std::string const & hash)
{
- mUpdateDownloader.download(uri);
+ mUpdateDownloader.download(uri, hash);
}
-void LLUpdaterServiceImpl::requiredUpdate(std::string const & newVersion, LLURI const & uri)
+void LLUpdaterServiceImpl::requiredUpdate(std::string const & newVersion,
+ LLURI const & uri,
+ std::string const & hash)
{
- mUpdateDownloader.download(uri);
+ mUpdateDownloader.download(uri, hash);
}
void LLUpdaterServiceImpl::upToDate(void)
diff --git a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
index 958526e35b..20d0f8fa09 100644
--- a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
+++ b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
@@ -64,7 +64,7 @@ void LLUpdateChecker::check(std::string const & protocolVersion, std::string con
std::string const & servicePath, std::string channel, std::string version)
{}
LLUpdateDownloader::LLUpdateDownloader(Client & ) {}
-void LLUpdateDownloader::download(LLURI const & ){}
+void LLUpdateDownloader::download(LLURI const & , std::string const &){}
/*****************************************************************************
* TUT