summaryrefslogtreecommitdiffstats
path: root/desktop/source/app/updater.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /desktop/source/app/updater.cxx
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--desktop/source/app/updater.cxx894
1 files changed, 894 insertions, 0 deletions
diff --git a/desktop/source/app/updater.cxx b/desktop/source/app/updater.cxx
new file mode 100644
index 000000000..b4be0f7bc
--- /dev/null
+++ b/desktop/source/app/updater.cxx
@@ -0,0 +1,894 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "updater.hxx"
+
+#if UNX
+#include <unistd.h>
+#include <errno.h>
+
+#endif
+
+#ifdef _WIN32
+#include <comphelper/windowsStart.hxx>
+#endif
+
+#include <fstream>
+#include <config_folders.h>
+#include <rtl/bootstrap.hxx>
+
+#include <officecfg/Office/Update.hxx>
+
+#include <rtl/ustring.hxx>
+#include <unotools/tempfile.hxx>
+#include <unotools/configmgr.hxx>
+#include <osl/file.hxx>
+#include <rtl/process.h>
+#include <sal/log.hxx>
+
+#include <curl/curl.h>
+
+#include <orcus/json_document_tree.hpp>
+#include <orcus/config.hpp>
+#include <orcus/pstring.hpp>
+#include <comphelper/hash.hxx>
+
+#include <com/sun/star/container/XNameAccess.hpp>
+
+#include <officecfg/Setup.hxx>
+
+#include <functional>
+#include <memory>
+#include <set>
+
+namespace {
+
+class error_updater : public std::exception
+{
+ OString maStr;
+public:
+
+ error_updater(const OString& rStr):
+ maStr(rStr)
+ {
+ }
+
+ virtual const char* what() const throw() override
+ {
+ return maStr.getStr();
+ }
+};
+
+#ifdef UNX
+static const char kUserAgent[] = "LibreOffice UpdateChecker/1.0 (Linux)";
+#else
+static const char kUserAgent[] = "LibreOffice UpdateChecker/1.0 (unknown platform)";
+#endif
+
+#ifdef UNX
+const char* const pUpdaterName = "updater";
+const char* const pSofficeExeName = "soffice";
+#elif defined(_WIN32)
+const char* pUpdaterName = "updater.exe";
+const char* pSofficeExeName = "soffice.exe";
+#else
+#error "Need implementation"
+#endif
+
+OUString normalizePath(const OUString& rPath)
+{
+ OUString aPath = rPath.replaceAll("//", "/");
+
+ // remove final /
+ if (aPath.endsWith("/"))
+ {
+ aPath = aPath.copy(0, aPath.getLength() - 1);
+ }
+
+ while (aPath.indexOf("/..") != -1)
+ {
+ sal_Int32 nIndex = aPath.indexOf("/..");
+ sal_Int32 i = nIndex - 1;
+ for (; i > 0; --i)
+ {
+ if (aPath[i] == '/')
+ break;
+ }
+
+ OUString aTempPath = aPath;
+ aPath = aTempPath.copy(0, i) + aPath.copy(nIndex + 3);
+ }
+
+ return aPath.replaceAll("\\", "/");
+}
+
+void CopyFileToDir(const OUString& rTempDirURL, const OUString & rFileName, const OUString& rOldDir)
+{
+ OUString aSourceURL = rOldDir + "/" + rFileName;
+ OUString aDestURL = rTempDirURL + "/" + rFileName;
+
+ osl::File::RC eError = osl::File::copy(aSourceURL, aDestURL);
+ if (eError != osl::File::E_None)
+ {
+ SAL_WARN("desktop.updater", "could not copy the file to a temp directory: " << rFileName);
+ throw std::exception();
+ }
+}
+
+OUString getPathFromURL(const OUString& rURL)
+{
+ OUString aPath;
+ osl::FileBase::getSystemPathFromFileURL(rURL, aPath);
+
+ return normalizePath(aPath);
+}
+
+void CopyUpdaterToTempDir(const OUString& rInstallDirURL, const OUString& rTempDirURL)
+{
+ OUString aUpdaterName = OUString::fromUtf8(pUpdaterName);
+ CopyFileToDir(rTempDirURL, aUpdaterName, rInstallDirURL);
+}
+
+#ifdef UNX
+typedef char CharT;
+#define tstrncpy std::strncpy
+#elif defined(_WIN32)
+typedef wchar_t CharT;
+#define tstrncpy std::wcsncpy
+#else
+#error "Need an implementation"
+#endif
+
+void createStr(const OUString& rStr, CharT** pArgs, size_t i)
+{
+#ifdef UNX
+ OString aStr = OUStringToOString(rStr, RTL_TEXTENCODING_UTF8);
+#elif defined(_WIN32)
+ OUString aStr = rStr;
+#else
+#error "Need an implementation"
+#endif
+ CharT* pStr = new CharT[aStr.getLength() + 1];
+ tstrncpy(pStr, (CharT*)aStr.getStr(), aStr.getLength());
+ pStr[aStr.getLength()] = '\0';
+ pArgs[i] = pStr;
+}
+
+CharT** createCommandLine()
+{
+ OUString aInstallDir = Updater::getInstallationPath();
+
+ size_t nCommandLineArgs = rtl_getAppCommandArgCount();
+ size_t nArgs = 8 + nCommandLineArgs;
+ CharT** pArgs = new CharT*[nArgs];
+ {
+ OUString aUpdaterName = OUString::fromUtf8(pUpdaterName);
+ createStr(aUpdaterName, pArgs, 0);
+ }
+ {
+ // directory with the patch log
+ OUString aPatchDir = Updater::getPatchDirURL();
+ rtl::Bootstrap::expandMacros(aPatchDir);
+ OUString aTempDirPath = getPathFromURL(aPatchDir);
+ Updater::log("Patch Dir: " + aTempDirPath);
+ createStr(aTempDirPath, pArgs, 1);
+ }
+ {
+ // the actual update directory
+ Updater::log("Install Dir: " + aInstallDir);
+ createStr(aInstallDir, pArgs, 2);
+ }
+ {
+ // the temporary updated build
+ Updater::log("Working Dir: " + aInstallDir);
+ createStr(aInstallDir, pArgs, 3);
+ }
+ {
+#ifdef UNX
+ OUString aPID("0");
+#elif defined(_WIN32)
+ oslProcessInfo aInfo;
+ aInfo.Size = sizeof(oslProcessInfo);
+ osl_getProcessInfo(nullptr, osl_Process_IDENTIFIER, &aInfo);
+ OUString aPID = OUString::number(aInfo.Ident);
+#else
+#error "Need an implementation"
+#endif
+ createStr(aPID, pArgs, 4);
+ }
+ {
+ OUString aExeDir = Updater::getExecutableDirURL();
+ OUString aSofficePath = getPathFromURL(aExeDir);
+ Updater::log("soffice Path: " + aSofficePath);
+ createStr(aSofficePath, pArgs, 5);
+ }
+ {
+ // the executable to start after the successful update
+ OUString aExeDir = Updater::getExecutableDirURL();
+ OUString aSofficePathURL = aExeDir + OUString::fromUtf8(pSofficeExeName);
+ OUString aSofficePath = getPathFromURL(aSofficePathURL);
+ createStr(aSofficePath, pArgs, 6);
+ }
+
+ // add the command line arguments from the soffice list
+ for (size_t i = 0; i < nCommandLineArgs; ++i)
+ {
+ OUString aCommandLineArg;
+ rtl_getAppCommandArg(i, &aCommandLineArg.pData);
+ createStr(aCommandLineArg, pArgs, 7 + i);
+ }
+
+ pArgs[nArgs - 1] = nullptr;
+
+ return pArgs;
+}
+
+struct update_file
+{
+ OUString aURL;
+ OUString aHash;
+ size_t nSize;
+};
+
+struct language_file
+{
+ update_file aUpdateFile;
+ OUString aLangCode;
+};
+
+struct update_info
+{
+ OUString aFromBuildID;
+ OUString aSeeAlsoURL;
+ OUString aMessage;
+
+ update_file aUpdateFile;
+ std::vector<language_file> aLanguageFiles;
+};
+
+bool isUserWritable(const OUString& rFileURL)
+{
+ osl::FileStatus aStatus(osl_FileStatus_Mask_Attributes);
+ osl::DirectoryItem aDirectoryItem;
+
+ osl::FileBase::RC eRes = osl::DirectoryItem::get(rFileURL, aDirectoryItem);
+ if (eRes != osl::FileBase::E_None)
+ {
+ Updater::log("Could not get the directory item for: " + rFileURL);
+ return false;
+ }
+
+ osl::FileBase::RC eResult = aDirectoryItem.getFileStatus(aStatus);
+ if (eResult != osl::FileBase::E_None)
+ {
+ Updater::log("Could not get the file status for: " + rFileURL);
+ return false;
+ }
+
+ bool bReadOnly = (aStatus.getAttributes() & static_cast<sal_uInt64>(osl_File_Attribute_ReadOnly)) != 0;
+ if (bReadOnly)
+ {
+ Updater::log("Update location as determined by: " + rFileURL + " is read-only.");
+ return false;
+ }
+
+ return true;
+}
+
+}
+
+bool update()
+{
+ utl::TempFile aTempDir(nullptr, true);
+ OUString aTempDirURL = aTempDir.GetURL();
+ CopyUpdaterToTempDir(Updater::getExecutableDirURL(), aTempDirURL);
+
+ OUString aUpdaterPath = getPathFromURL(aTempDirURL + "/" + OUString::fromUtf8(pUpdaterName));
+
+ Updater::log("Calling the updater with parameters: ");
+ CharT** pArgs = createCommandLine();
+
+ bool bSuccess = true;
+ const char* pUpdaterTestReplace = std::getenv("LIBO_UPDATER_TEST_REPLACE");
+ if (!pUpdaterTestReplace)
+ {
+#if UNX
+ OString aPath = OUStringToOString(aUpdaterPath, RTL_TEXTENCODING_UTF8);
+ if (execv(aPath.getStr(), pArgs))
+ {
+ printf("execv failed with error %d %s\n",errno,strerror(errno));
+ bSuccess = false;
+ }
+#elif defined(_WIN32)
+ bSuccess = WinLaunchChild((wchar_t*)aUpdaterPath.getStr(), 8, pArgs);
+#endif
+ }
+ else
+ {
+ SAL_WARN("desktop.updater", "Updater executable path: " << aUpdaterPath);
+ for (size_t i = 0; i < 8 + rtl_getAppCommandArgCount(); ++i)
+ {
+ SAL_WARN("desktop.updater", pArgs[i]);
+ }
+ bSuccess = false;
+ }
+
+ for (size_t i = 0; i < 8 + rtl_getAppCommandArgCount(); ++i)
+ {
+ delete[] pArgs[i];
+ }
+ delete[] pArgs;
+
+ return bSuccess;
+}
+
+namespace {
+
+// Callback to get the response data from server.
+size_t WriteCallback(void *ptr, size_t size,
+ size_t nmemb, void *userp)
+{
+ if (!userp)
+ return 0;
+
+ std::string* response = static_cast<std::string *>(userp);
+ size_t real_size = size * nmemb;
+ response->append(static_cast<char *>(ptr), real_size);
+ return real_size;
+}
+
+
+
+class invalid_update_info : public std::exception
+{
+};
+
+class invalid_hash : public std::exception
+{
+ OString maMessage;
+public:
+
+ invalid_hash(const OUString& rExpectedHash, const OUString& rReceivedHash)
+ : maMessage(
+ OUStringToOString(
+ OUString("Invalid hash found.\nExpected: " + rExpectedHash + ";\nReceived: " + rReceivedHash),
+ RTL_TEXTENCODING_UTF8)
+ )
+ {
+ }
+
+ const char* what() const noexcept override
+ {
+ return maMessage.getStr();
+ }
+};
+
+class invalid_size : public std::exception
+{
+ OString maMessage;
+public:
+
+ invalid_size(const size_t nExpectedSize, const size_t nReceivedSize)
+ : maMessage(
+ OUStringToOString(
+ OUString("Invalid file size found.\nExpected: " + OUString::number(nExpectedSize) + ";\nReceived: " + OUString::number(nReceivedSize)),
+ RTL_TEXTENCODING_UTF8)
+ )
+ {
+ }
+
+ const char* what() const noexcept override
+ {
+ return maMessage.getStr();
+ }
+};
+
+OUString toOUString(const std::string& rStr)
+{
+ return OUString::fromUtf8(rStr.c_str());
+}
+
+update_file parse_update_file(orcus::json::node& rNode)
+{
+ if (rNode.type() != orcus::json::node_t::object)
+ {
+ SAL_WARN("desktop.updater", "invalid update or language file entry");
+ throw invalid_update_info();
+ }
+
+ if (rNode.child_count() < 4)
+ {
+ SAL_WARN("desktop.updater", "invalid update or language file entry");
+ throw invalid_update_info();
+ }
+
+ orcus::json::node aURLNode = rNode.child("url");
+ orcus::json::node aHashNode = rNode.child("hash");
+ orcus::json::node aHashTypeNode = rNode.child("hash_function");
+ orcus::json::node aSizeNode = rNode.child("size");
+
+ if (aHashTypeNode.string_value() != "sha512")
+ {
+ SAL_WARN("desktop.updater", "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 = static_cast<sal_uInt32>(aSizeNode.numeric_value());
+ 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.updater", "invalid root entries: " << rResponse);
+ throw invalid_update_info();
+ }
+
+ auto aRootKeys = aDocumentRoot.keys();
+ if (std::find(aRootKeys.begin(), aRootKeys.end(), "error") != aRootKeys.end())
+ {
+ throw invalid_update_info();
+ }
+ else if (std::find(aRootKeys.begin(), aRootKeys.end(), "response") != aRootKeys.end())
+ {
+ update_info aUpdateInfo;
+ auto aMsgNode = aDocumentRoot.child("response");
+ aUpdateInfo.aMessage = toOUString(aMsgNode.string_value().str());
+ return aUpdateInfo;
+ }
+
+ orcus::json::node aFromNode = aDocumentRoot.child("from");
+ if (aFromNode.type() != orcus::json::node_t::string)
+ {
+ throw invalid_update_info();
+ }
+
+ orcus::json::node aSeeAlsoNode = aDocumentRoot.child("see also");
+ if (aSeeAlsoNode.type() != orcus::json::node_t::string)
+ {
+ throw invalid_update_info();
+ }
+
+ orcus::json::node aUpdateNode = aDocumentRoot.child("update");
+ if (aUpdateNode.type() != orcus::json::node_t::object)
+ {
+ throw invalid_update_info();
+ }
+
+ orcus::json::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.aSeeAlsoURL = toOUString(aSeeAlsoNode.string_value().str());
+
+ aUpdateInfo.aUpdateFile = parse_update_file(aUpdateNode);
+
+ std::vector<orcus::pstring> aLanguages = aLanguageNode.keys();
+ for (auto const& language : aLanguages)
+ {
+ language_file aLanguageFile;
+ auto aLangEntry = aLanguageNode.child(language);
+ aLanguageFile.aLangCode = toOUString(language.str());
+ aLanguageFile.aUpdateFile = parse_update_file(aLangEntry);
+ aUpdateInfo.aLanguageFiles.push_back(aLanguageFile);
+ }
+
+ return aUpdateInfo;
+}
+
+struct WriteDataFile
+{
+ comphelper::Hash maHash;
+ SvStream* mpStream;
+
+ WriteDataFile(SvStream* pStream):
+ maHash(comphelper::HashType::SHA512),
+ mpStream(pStream)
+ {
+ }
+
+ OUString getHash()
+ {
+ auto final_hash = maHash.finalize();
+ std::stringstream aStrm;
+ for (auto& i: final_hash)
+ {
+ aStrm << std::setw(2) << std::setfill('0') << std::hex << (int)i;
+ }
+
+ return toOUString(aStrm.str());
+ }
+};
+
+// Callback to get the response data from server to a file.
+size_t WriteCallbackFile(void *ptr, size_t size,
+ size_t nmemb, void *userp)
+{
+ if (!userp)
+ return 0;
+
+ WriteDataFile* response = static_cast<WriteDataFile *>(userp);
+ size_t real_size = size * nmemb;
+ response->mpStream->WriteBytes(ptr, real_size);
+ response->maHash.update(static_cast<const unsigned char*>(ptr), real_size);
+ return real_size;
+}
+
+std::string download_content(const OString& rURL, bool bFile, OUString& rHash)
+{
+ Updater::log("Download: " + rURL);
+ std::unique_ptr<CURL, std::function<void(CURL *)>> curl(
+ curl_easy_init(), [](CURL * p) { curl_easy_cleanup(p); });
+
+ if (!curl)
+ return std::string();
+
+ curl_easy_setopt(curl.get(), CURLOPT_URL, rURL.getStr());
+ curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, kUserAgent);
+ bool bUseProxy = false;
+ if (bUseProxy)
+ {
+ /*
+ curl_easy_setopt(curl, CURLOPT_PROXY, proxy.c_str());
+ curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, proxy_user_pwd.c_str());
+ */
+ }
+
+ char buf[] = "Expect:";
+ curl_slist* headerlist = nullptr;
+ headerlist = curl_slist_append(headerlist, buf);
+ curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, headerlist);
+ curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1); // follow redirects
+ // only allow redirect to https://
+#if (LIBCURL_VERSION_MAJOR > 7) || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 85)
+ curl_easy_setopt(curl.get(), CURLOPT_REDIR_PROTOCOLS_STR, "https");
+#else
+ curl_easy_setopt(curl.get(), CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
+#endif
+
+ std::string response_body;
+ utl::TempFile aTempFile;
+ WriteDataFile aFile(aTempFile.GetStream(StreamMode::WRITE));
+ if (!bFile)
+ {
+ curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallback);
+ curl_easy_setopt(curl.get(), 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.get(), CURLOPT_WRITEFUNCTION, WriteCallbackFile);
+ curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA,
+ static_cast<void *>(&aFile));
+ }
+
+ // Fail if 400+ is returned from the web server.
+ curl_easy_setopt(curl.get(), CURLOPT_FAILONERROR, 1);
+
+ CURLcode cc = curl_easy_perform(curl.get());
+ long http_code = 0;
+ curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &http_code);
+ if (http_code != 200)
+ {
+ SAL_WARN("desktop.updater", "download did not succeed. Error code: " << http_code);
+ throw error_updater("download did not succeed");
+ }
+
+ if (cc != CURLE_OK)
+ {
+ SAL_WARN("desktop.updater", "curl error: " << cc);
+ throw error_updater("curl error");
+ }
+
+ if (bFile)
+ rHash = aFile.getHash();
+
+ return response_body;
+}
+
+void handle_file_error(osl::FileBase::RC eError, const OUString& rMsg)
+{
+ switch (eError)
+ {
+ case osl::FileBase::E_None:
+ break;
+ default:
+ SAL_WARN("desktop.updater", "file error code: " << eError << ", " << rMsg);
+ throw error_updater(OUStringToOString(rMsg, RTL_TEXTENCODING_UTF8));
+ }
+}
+
+void download_file(const OUString& rURL, size_t nFileSize, const OUString& rHash, const OUString& aFileName)
+{
+ Updater::log("Download File: " + rURL + "; FileName: " + aFileName);
+ OString aURL = OUStringToOString(rURL, RTL_TEXTENCODING_UTF8);
+ OUString aHash;
+ std::string temp_file = download_content(aURL, true, aHash);
+ if (temp_file.empty())
+ throw error_updater("empty temp file string");
+
+ OUString aTempFile = OUString::fromUtf8(temp_file.c_str());
+ Updater::log("TempFile: " + aTempFile);
+ osl::File aDownloadedFile(aTempFile);
+ osl::FileBase::RC eError = aDownloadedFile.open(1);
+ handle_file_error(eError, "Could not open the download file: " + aTempFile);
+
+ sal_uInt64 nSize = 0;
+ eError = aDownloadedFile.getSize(nSize);
+ handle_file_error(eError, "Could not get the file size of the downloaded file: " + aTempFile);
+ if (nSize != nFileSize)
+ {
+ SAL_WARN("desktop.updater", "File sizes don't match. File might be corrupted.");
+ throw invalid_size(nFileSize, nSize);
+ }
+
+ if (aHash != rHash)
+ {
+ SAL_WARN("desktop.updater", "File hash don't match. File might be corrupted.");
+ throw invalid_hash(rHash, aHash);
+ }
+
+ 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;
+ Updater::log("Destination File: " + aDestFile);
+ aDownloadedFile.close();
+ eError = osl::File::move(aTempFile, aDestFile);
+ handle_file_error(eError, "Could not move the file from the Temp directory to the user config: TempFile: " + aTempFile + "; DestFile: " + aDestFile);
+}
+
+}
+
+void update_checker()
+{
+ OUString aBrandBaseDir("${BRAND_BASE_DIR}");
+ rtl::Bootstrap::expandMacros(aBrandBaseDir);
+ bool bUserWritable = isUserWritable(aBrandBaseDir);
+ if (!bUserWritable)
+ {
+ Updater::log("Can't update as the update location is not user writable");
+ return;
+ }
+
+ OUString aDownloadCheckBaseURL = officecfg::Office::Update::Update::URL::get();
+ static const char* pDownloadCheckBaseURLEnv = std::getenv("LIBO_UPDATER_URL");
+ if (pDownloadCheckBaseURLEnv)
+ {
+ aDownloadCheckBaseURL = OUString::createFromAscii(pDownloadCheckBaseURLEnv);
+ }
+
+ OUString aProductName = utl::ConfigManager::getProductName();
+ OUString aBuildID = Updater::getBuildID();
+
+ static const char* pBuildIdEnv = std::getenv("LIBO_UPDATER_BUILD");
+ if (pBuildIdEnv)
+ {
+ aBuildID = OUString::createFromAscii(pBuildIdEnv);
+ }
+
+ OUString aBuildTarget = "${_OS}_${_ARCH}";
+ rtl::Bootstrap::expandMacros(aBuildTarget);
+ OUString aChannel = Updater::getUpdateChannel();
+ static const char* pUpdateChannelEnv = std::getenv("LIBO_UPDATER_CHANNEL");
+ if (pUpdateChannelEnv)
+ {
+ aChannel = OUString::createFromAscii(pUpdateChannelEnv);
+ }
+
+ OUString aDownloadCheckURL = aDownloadCheckBaseURL + "update/check/1/" + aProductName +
+ "/" + aBuildID + "/" + aBuildTarget + "/" + aChannel;
+ OString aURL = OUStringToOString(aDownloadCheckURL, RTL_TEXTENCODING_UTF8);
+ Updater::log("Update check: " + aURL);
+
+ try
+ {
+ OUString aHash;
+ std::string response_body = download_content(aURL, false, aHash);
+ if (!response_body.empty())
+ {
+
+ update_info aUpdateInfo = parse_response(response_body);
+ if (aUpdateInfo.aUpdateFile.aURL.isEmpty())
+ {
+ // No update currently available
+ // add entry to updating.log with the message
+ SAL_WARN("desktop.updater", "Message received from the updater: " << aUpdateInfo.aMessage);
+ Updater::log("Server response: " + aUpdateInfo.aMessage);
+ }
+ else
+ {
+ css::uno::Sequence<OUString> aInstalledLanguages(officecfg::Setup::Office::InstalledLocales::get()->getElementNames());
+ std::set<OUString> aInstalledLanguageSet(std::begin(aInstalledLanguages), std::end(aInstalledLanguages));
+ download_file(aUpdateInfo.aUpdateFile.aURL, aUpdateInfo.aUpdateFile.nSize, aUpdateInfo.aUpdateFile.aHash, "update.mar");
+ for (auto& lang_update : aUpdateInfo.aLanguageFiles)
+ {
+ // only download the language packs for installed languages
+ if (aInstalledLanguageSet.find(lang_update.aLangCode) != aInstalledLanguageSet.end())
+ {
+ OUString aFileName = "update_" + lang_update.aLangCode + ".mar";
+ download_file(lang_update.aUpdateFile.aURL, lang_update.aUpdateFile.nSize, lang_update.aUpdateFile.aHash, aFileName);
+ }
+ }
+ OUString aSeeAlsoURL = aUpdateInfo.aSeeAlsoURL;
+ std::shared_ptr< comphelper::ConfigurationChanges > batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Update::Update::SeeAlso::set(aSeeAlsoURL, batch);
+ batch->commit();
+ }
+ }
+ }
+ catch (const invalid_update_info&)
+ {
+ SAL_WARN("desktop.updater", "invalid update information");
+ Updater::log(OString("warning: invalid update info"));
+ }
+ catch (const error_updater& e)
+ {
+ SAL_WARN("desktop.updater", "error during the update check: " << e.what());
+ Updater::log(OString("warning: error by the updater") + e.what());
+ }
+ catch (const invalid_size& e)
+ {
+ SAL_WARN("desktop.updater", e.what());
+ Updater::log(OString("warning: invalid size"));
+ }
+ catch (const invalid_hash& e)
+ {
+ SAL_WARN("desktop.updater", e.what());
+ Updater::log(OString("warning: invalid hash"));
+ }
+ catch (...)
+ {
+ SAL_WARN("desktop.updater", "unknown error during the update check");
+ Updater::log(OString("warning: unknown exception"));
+ }
+}
+
+OUString Updater::getUpdateInfoLog()
+{
+ OUString aUpdateInfoURL("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/patch/updating.log");
+ rtl::Bootstrap::expandMacros(aUpdateInfoURL);
+
+ return aUpdateInfoURL;
+}
+
+OUString Updater::getPatchDirURL()
+{
+ OUString aPatchDirURL("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/patch/");
+ rtl::Bootstrap::expandMacros(aPatchDirURL);
+
+ return aPatchDirURL;
+}
+
+OUString Updater::getUpdateFileURL()
+{
+ return getPatchDirURL() + "update.mar";
+}
+
+OUString Updater::getInstallationPath()
+{
+ OUString aInstallDir( "$BRAND_BASE_DIR/");
+ rtl::Bootstrap::expandMacros(aInstallDir);
+
+ return getPathFromURL(aInstallDir);
+}
+
+OUString Updater::getExecutableDirURL()
+{
+ OUString aExeDir( "$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/" );
+ rtl::Bootstrap::expandMacros(aExeDir);
+
+ return aExeDir;
+}
+
+void Updater::log(const OUString& rMessage)
+{
+ SAL_INFO("desktop.updater", rMessage);
+ OUString aUpdateLog = getUpdateInfoLog();
+ SvFileStream aLog(aUpdateLog, StreamMode::STD_READWRITE);
+ aLog.Seek(aLog.Tell() + aLog.remainingSize()); // make sure we are at the end
+ aLog.WriteLine(OUStringToOString(rMessage, RTL_TEXTENCODING_UTF8));
+}
+
+void Updater::log(const OString& rMessage)
+{
+ SAL_INFO("desktop.updater", rMessage);
+ OUString aUpdateLog = getUpdateInfoLog();
+ SvFileStream aLog(aUpdateLog, StreamMode::STD_READWRITE);
+ aLog.Seek(aLog.Tell() + aLog.remainingSize()); // make sure we are at the end
+ aLog.WriteLine(rMessage);
+}
+
+void Updater::log(const char* pMessage)
+{
+ SAL_INFO("desktop.updater", pMessage);
+ OUString aUpdateLog = getUpdateInfoLog();
+ SvFileStream aLog(aUpdateLog, StreamMode::STD_READWRITE);
+ aLog.Seek(aLog.Tell() + aLog.remainingSize()); // make sure we are at the end
+ aLog.WriteCharPtr(pMessage);
+}
+
+OUString Updater::getBuildID()
+{
+ OUString aBuildID("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}");
+ rtl::Bootstrap::expandMacros(aBuildID);
+
+ return aBuildID;
+}
+
+OUString Updater::getUpdateChannel()
+{
+ OUString aUpdateChannel("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":UpdateChannel}");
+ rtl::Bootstrap::expandMacros(aUpdateChannel);
+
+ return aUpdateChannel;
+}
+
+void Updater::removeUpdateFiles()
+{
+ Updater::log("Removing: " + getUpdateFileURL());
+ osl::File::remove(getUpdateFileURL());
+
+ OUString aPatchDirURL = getPatchDirURL();
+ osl::Directory aDir(aPatchDirURL);
+ aDir.open();
+
+ osl::FileBase::RC eRC;
+ do
+ {
+ osl::DirectoryItem aItem;
+ eRC = aDir.getNextItem(aItem);
+ if (eRC == osl::FileBase::E_None)
+ {
+ osl::FileStatus aStatus(osl_FileStatus_Mask_All);
+ if (aItem.getFileStatus(aStatus) != osl::FileBase::E_None)
+ continue;
+
+ if (!aStatus.isRegular())
+ continue;
+
+ OUString aURL = aStatus.getFileURL();
+ if (!aURL.endsWith(".mar"))
+ continue;
+
+ Updater::log("Removing. " + aURL);
+ osl::File::remove(aURL);
+ }
+ }
+ while (eRC == osl::FileBase::E_None);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */