diff options
author | Markus Mohrhard <markus.mohrhard@googlemail.com> | 2016-08-27 23:52:39 +0200 |
---|---|---|
committer | Markus Mohrhard <markus.mohrhard@googlemail.com> | 2017-05-19 03:43:19 +0200 |
commit | 569269078576fa832143ec4f0bf03283ff358f48 (patch) | |
tree | 0387a4f7b5b30074853cec8343b3adebe344b62f | |
parent | 5b5e6406cc6c49ab09c075b091d444d26ca68985 (diff) |
improve the update checker
Change-Id: I78fff95e835d5480c84fc334ee6cd716be531163
-rw-r--r-- | desktop/Library_sofficeapp.mk | 3 | ||||
-rw-r--r-- | desktop/source/app/updater.cxx | 324 |
2 files changed, 300 insertions, 27 deletions
diff --git a/desktop/Library_sofficeapp.mk b/desktop/Library_sofficeapp.mk index aabe8705fae4..6998060d92dc 100644 --- a/desktop/Library_sofficeapp.mk +++ b/desktop/Library_sofficeapp.mk @@ -32,6 +32,9 @@ $(eval $(call gb_Library_use_externals,sofficeapp, \ icui18n \ icuuc \ curl \ + $(if $(ENABLE_ONLINE_UPDATE_MAR),\ + orcus-parser \ + orcus )\ )) $(eval $(call gb_Library_use_custom_headers,sofficeapp,\ diff --git a/desktop/source/app/updater.cxx b/desktop/source/app/updater.cxx index b6bac978fb21..1856d0c3e4dd 100644 --- a/desktop/source/app/updater.cxx +++ b/desktop/source/app/updater.cxx @@ -11,6 +11,7 @@ #include <unistd.h> #include <errno.h> +#include <fstream> #include <config_folders.h> #include <rtl/bootstrap.hxx> @@ -24,8 +25,16 @@ #include <curl/curl.h> +#include <orcus/json_document_tree.hpp> +#include <orcus/config.hpp> +#include <orcus/pstring.hpp> + namespace { +class error_updater : public std::exception +{ +}; + static const char kUserAgent[] = "UpdateChecker/1.0 (Linux)"; const char* pUpdaterName = "updater"; @@ -166,35 +175,167 @@ static size_t WriteCallback(void *ptr, size_t size, return real_size; } +// Callback to get the response data from server to a file. +static size_t WriteCallbackFile(void *ptr, size_t size, + size_t nmemb, void *userp) +{ + if (!userp) + return 0; + + SvStream* response = static_cast<SvStream *>(userp); + size_t real_size = size * nmemb; + response->WriteBytes(static_cast<char *>(ptr), real_size); + return real_size; } -void update_checker() + +class invalid_update_info : public std::exception { - OUString aDownloadCheckBaseURL = officecfg::Office::Update::Update::URL::get(); +}; - OUString aProductName = utl::ConfigManager::getProductName(); - OUString aBuildID("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}"); - rtl::Bootstrap::expandMacros(aBuildID); - OUString aVersion = "5.3.0.0.alpha0+"; - OUString aBuildTarget = "${_OS}-${_ARCH}"; - rtl::Bootstrap::expandMacros(aBuildTarget); - OUString aLocale = "en-US"; - OUString aChannel = officecfg::Office::Update::Update::UpdateChannel::get(); - OUString aOSVersion = "0"; +struct update_file +{ + OUString aURL; + OUString aHash; + size_t nSize; +}; - OUString aDownloadCheckURL = aDownloadCheckBaseURL + "update/3/" + aProductName + - "/" + aVersion + "/" + aBuildID + "/" + aBuildTarget + "/" + aLocale + - "/" + aChannel + "/" + aOSVersion + "/default/default/update.xml?force=1"; - OString aURL = OUStringToOString(aDownloadCheckURL, RTL_TEXTENCODING_UTF8); - SAL_DEBUG(aDownloadCheckURL); - SAL_DEBUG("update_checker"); +struct language_file +{ + update_file aUpdateFile; + OUString aLangCode; +}; + +struct update_info +{ + OUString aFromBuildID; + OUString aSeeAlsoURL; + OUString aNewVersion; + + update_file aUpdateFile; + std::vector<language_file> aLanguageFiles; +}; + +OUString toOUString(const std::string& rStr) +{ + return OUString::fromUtf8(rStr.c_str()); +} + +update_file parse_update_file(const orcus::json::detail::node& rNode) +{ + if (rNode.type() != orcus::json::detail::node_t::object) + { + SAL_WARN("desktop.update", "invalid update or language file entry"); + throw invalid_update_info(); + } + + if (rNode.child_count() != 4) + { + SAL_WARN("desktop.update", "invalid update or language file entry"); + throw invalid_update_info(); + } + + orcus::json::detail::node aURLNode = rNode.child("url"); + orcus::json::detail::node aHashNode = rNode.child("hash"); + orcus::json::detail::node aHashTypeNode = rNode.child("hash_function"); + orcus::json::detail::node aSizeNode = rNode.child("size"); + + if (aHashTypeNode.string_value() != "sha512") + { + SAL_WARN("desktop.update", "invalid hash type"); + throw invalid_update_info(); + } + + update_file aUpdateFile; + aUpdateFile.aURL = toOUString(aURLNode.string_value().str()); + + if (aUpdateFile.aURL.isEmpty()) + throw invalid_update_info(); + + aUpdateFile.aHash = toOUString(aHashNode.string_value().str()); + aUpdateFile.nSize = toOUString(aSizeNode.string_value().str()).toUInt32(); + + return aUpdateFile; +} + +update_info parse_response(const std::string& rResponse) +{ + orcus::json_document_tree aJsonDoc; + orcus::json_config aConfig; + aJsonDoc.load(rResponse, aConfig); + + auto aDocumentRoot = aJsonDoc.get_document_root(); + if (aDocumentRoot.type() != orcus::json_node_t::object) + { + SAL_WARN("desktop.Update", "invalid root entries: " << rResponse); + throw invalid_update_info(); + } + + auto aRootKeys = aDocumentRoot.keys(); + if (aRootKeys.size() != 5) + { + SAL_WARN("desktop.Update", "invalid root entries: " << rResponse); + throw invalid_update_info(); + } + + orcus::json::detail::node aFromNode = aDocumentRoot.child("from"); + if (aFromNode.type() != orcus::json_node_t::string) + { + throw invalid_update_info(); + } + + orcus::json::detail::node aSeeAlsoNode = aDocumentRoot.child("see also"); + if (aSeeAlsoNode.type() != orcus::json_node_t::string) + { + throw invalid_update_info(); + } + orcus::json::detail::node aVersionNode = aDocumentRoot.child("version"); + if (aVersionNode.type() != orcus::json_node_t::string) + { + throw invalid_update_info(); + } + + orcus::json::detail::node aUpdateNode = aDocumentRoot.child("update"); + if (aUpdateNode.type() != orcus::json_node_t::object) + { + throw invalid_update_info(); + } + + orcus::json::detail::node aLanguageNode = aDocumentRoot.child("languages"); + if (aUpdateNode.type() != orcus::json_node_t::object) + { + throw invalid_update_info(); + } + + update_info aUpdateInfo; + aUpdateInfo.aFromBuildID = toOUString(aFromNode.string_value().str()); + aUpdateInfo.aNewVersion = toOUString(aVersionNode.string_value().str()); + aUpdateInfo.aSeeAlsoURL = toOUString(aSeeAlsoNode.string_value().str()); + + aUpdateInfo.aUpdateFile = parse_update_file(aUpdateNode); + + std::vector<orcus::pstring> aLanguages = aLanguageNode.keys(); + for (auto itr = aLanguages.begin(), itrEnd = aLanguages.end(); itr != itrEnd; ++itr) + { + language_file aLanguageFile; + auto aLangEntry = aLanguageNode.child(*itr); + aLanguageFile.aLangCode = toOUString(itr->str()); + aLanguageFile.aUpdateFile = parse_update_file(aLangEntry); + aUpdateInfo.aLanguageFiles.push_back(aLanguageFile); + } + + return aUpdateInfo; +} + +std::string download_content(const OString& rURL, bool bFile) +{ CURL* curl = curl_easy_init(); if (!curl) - return; + return std::string(); - curl_easy_setopt(curl, CURLOPT_URL, aURL.getStr()); + curl_easy_setopt(curl, CURLOPT_URL, rURL.getStr()); curl_easy_setopt(curl, CURLOPT_USERAGENT, kUserAgent); bool bUseProxy = false; if (bUseProxy) @@ -210,10 +351,28 @@ void update_checker() headerlist = curl_slist_append(headerlist, buf); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); std::string response_body; - curl_easy_setopt(curl, CURLOPT_WRITEDATA, - static_cast<void *>(&response_body)); + utl::TempFile aTempFile; + if (!bFile) + { + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, + static_cast<void *>(&response_body)); + + aTempFile.EnableKillingFile(true); + } + else + { + OUString aTempFileURL = aTempFile.GetURL(); + OString aTempFileURLOString = OUStringToOString(aTempFileURL, RTL_TEXTENCODING_UTF8); + response_body.append(aTempFileURLOString.getStr(), aTempFileURLOString.getLength()); + + aTempFile.EnableKillingFile(false); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallbackFile); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, + static_cast<void *>(aTempFile.GetStream(StreamMode::WRITE))); + } // Fail if 400+ is returned from the web server. curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); @@ -221,12 +380,123 @@ void update_checker() CURLcode cc = curl_easy_perform(curl); long http_code = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); - SAL_DEBUG(http_code); - SAL_DEBUG(cc); - SAL_DEBUG(response_body); - if (cc == CURLE_OK) + if (http_code != 200) + { + SAL_WARN("desktop.updater", "download did not succeed. Error code: " << http_code); + throw error_updater(); + } + + if (cc != CURLE_OK) + { + SAL_WARN("desktop.updater", "curl error: " << cc); + throw error_updater(); + } + + return response_body; +} + +OUString generateHash(const OUString& rURL) +{ + return OUString(); +} + +void handle_file_error(osl::FileBase::RC eError) +{ + switch (eError) + { + case osl::FileBase::E_None: + break; + default: + SAL_WARN("desktop.updater", "file error code: " << eError); + throw error_updater(); + } +} + +void download_file(const OUString& rURL, size_t nFileSize, const OUString& rHash, const OUString& aFileName) +{ + OString aURL = OUStringToOString(rURL, RTL_TEXTENCODING_UTF8); + std::string temp_file = download_content(aURL, true); + if (temp_file.empty()) + throw error_updater(); + + OUString aTempFile = OUString::fromUtf8(temp_file.c_str()); + osl::File aDownloadedFile(aTempFile); + osl::FileBase::RC eError = aDownloadedFile.open(1); + handle_file_error(eError); + + sal_uInt64 nSize = 0; + eError = aDownloadedFile.getSize(nSize); + handle_file_error(eError); + if (nSize != nFileSize) + { + SAL_WARN("desktop.updater", "File sizes don't match. File might be corrupted."); + } + + OUString aHash = generateHash(aTempFile); + if (aHash != rHash) + { + SAL_WARN("desktop.updater", "File hash don't match. File might be corrupted."); + } + + OUString aPatchDirURL("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/patch/"); + rtl::Bootstrap::expandMacros(aPatchDirURL); + osl::Directory::create(aPatchDirURL); + + OUString aDestFile = aPatchDirURL + aFileName; + eError = osl::File::move(aTempFile, aDestFile); + handle_file_error(eError); +} + +} + +void update_checker() +{ + OUString aDownloadCheckBaseURL = officecfg::Office::Update::Update::URL::get(); + + OUString aProductName = utl::ConfigManager::getProductName(); + OUString aBuildID("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}"); + rtl::Bootstrap::expandMacros(aBuildID); + OUString aVersion = "5.3.0.0.alpha0+"; + OUString aBuildTarget = "${_OS}-${_ARCH}"; + rtl::Bootstrap::expandMacros(aBuildTarget); + OUString aLocale = "en-US"; + OUString aChannel = officecfg::Office::Update::Update::UpdateChannel::get(); + OUString aOSVersion = "0"; + + OUString aDownloadCheckURL = aDownloadCheckBaseURL + "update/3/" + aProductName + + "/" + aVersion + "/" + aBuildID + "/" + aBuildTarget + "/" + aLocale + + "/" + aChannel + "/" + aOSVersion + "/default/default/update.xml?force=1"; + OString aURL = OUStringToOString(aDownloadCheckURL, RTL_TEXTENCODING_UTF8); + +#if 0 + std::string response_body = download_content(aURL, false); +#else + std::string response_body; + if(std::ifstream is{"/lo/users/moggi/update.json", std::ios::binary | std::ios::ate}) + { + auto size = is.tellg(); + std::string str(size, '\0'); // construct string to stream size + is.seekg(0); + is.read(&str[0], size); + response_body = str; + } +#endif + + try + { + if (!response_body.empty()) + { + update_info aUpdateInfo = parse_response(response_body); + download_file(aUpdateInfo.aUpdateFile.aURL, aUpdateInfo.aUpdateFile.nSize, aUpdateInfo.aUpdateFile.aHash, "update.mar"); + } + } + catch (const invalid_update_info&) + { + SAL_WARN("desktop.updater", "invalid update information"); + } + catch (const error_updater&) { - SAL_DEBUG(response_body); + SAL_WARN("desktop.updater", "error during the update check"); } } |