23#include <config_folders.h>
24#include <rtl/bootstrap.hxx>
26#include <officecfg/Office/Update.hxx>
28#include <rtl/ustring.hxx>
31#include <osl/file.hxx>
32#include <rtl/process.h>
37#include <orcus/json_document_tree.hpp>
38#include <orcus/config.hpp>
39#include <orcus/pstring.hpp>
42#include <com/sun/star/container/XNameAccess.hpp>
44#include <officecfg/Setup.hxx>
52class error_updater :
public std::exception
57 error_updater(
const OString& rStr):
62 virtual const char* what()
const throw()
override
64 return maStr.getStr();
69static const char kUserAgent[] =
"LibreOffice UpdateChecker/1.0 (Linux)";
71static const char kUserAgent[] =
"LibreOffice UpdateChecker/1.0 (unknown platform)";
75const char*
const pUpdaterName =
"updater";
76const char*
const pSofficeExeName =
"soffice";
78const char* pUpdaterName =
"updater.exe";
79const char* pSofficeExeName =
"soffice.exe";
81#error "Need implementation"
84OUString normalizePath(
const OUString& rPath)
86 OUString aPath = rPath.replaceAll(
"//",
"/");
89 if (aPath.endsWith(
"/"))
91 aPath = aPath.copy(0, aPath.getLength() - 1);
94 while (aPath.indexOf(
"/..") != -1)
96 sal_Int32
nIndex = aPath.indexOf(
"/..");
104 OUString aTempPath = aPath;
105 aPath = aTempPath.copy(0, i) + aPath.copy(nIndex + 3);
108 return aPath.replaceAll(
"\\",
"/");
111void CopyFileToDir(
const OUString& rTempDirURL,
const OUString & rFileName,
const OUString& rOldDir)
113 OUString aSourceURL = rOldDir +
"/" + rFileName;
114 OUString aDestURL = rTempDirURL +
"/" + rFileName;
116 osl::File::RC eError = osl::File::copy(aSourceURL, aDestURL);
117 if (eError != osl::File::E_None)
119 SAL_WARN(
"desktop.updater",
"could not copy the file to a temp directory: " << rFileName);
120 throw std::exception();
124OUString getPathFromURL(
const OUString& rURL)
127 osl::FileBase::getSystemPathFromFileURL(rURL, aPath);
129 return normalizePath(aPath);
132void CopyUpdaterToTempDir(
const OUString& rInstallDirURL,
const OUString& rTempDirURL)
134 OUString aUpdaterName = OUString::fromUtf8(pUpdaterName);
135 CopyFileToDir(rTempDirURL, aUpdaterName, rInstallDirURL);
140#define tstrncpy std::strncpy
142typedef wchar_t CharT;
143#define tstrncpy std::wcsncpy
145#error "Need an implementation"
148void createStr(
const OUString& rStr, CharT** pArgs,
size_t i)
153 OUString
aStr = rStr;
155#error "Need an implementation"
157 CharT* pStr =
new CharT[
aStr.getLength() + 1];
159 pStr[
aStr.getLength()] =
'\0';
163CharT** createCommandLine()
167 size_t nCommandLineArgs = rtl_getAppCommandArgCount();
168 size_t nArgs = 8 + nCommandLineArgs;
169 CharT** pArgs =
new CharT*[nArgs];
171 OUString aUpdaterName = OUString::fromUtf8(pUpdaterName);
172 createStr(aUpdaterName, pArgs, 0);
177 rtl::Bootstrap::expandMacros(aPatchDir);
178 OUString aTempDirPath = getPathFromURL(aPatchDir);
180 createStr(aTempDirPath, pArgs, 1);
185 createStr(aInstallDir, pArgs, 2);
190 createStr(aInstallDir, pArgs, 3);
196 oslProcessInfo aInfo;
197 aInfo.Size =
sizeof(oslProcessInfo);
198 osl_getProcessInfo(
nullptr, osl_Process_IDENTIFIER, &aInfo);
199 OUString aPID = OUString::number(aInfo.Ident);
201#error "Need an implementation"
203 createStr(aPID, pArgs, 4);
207 OUString aSofficePath = getPathFromURL(aExeDir);
209 createStr(aSofficePath, pArgs, 5);
214 OUString aSofficePathURL = aExeDir + OUString::fromUtf8(pSofficeExeName);
215 OUString aSofficePath = getPathFromURL(aSofficePathURL);
216 createStr(aSofficePath, pArgs, 6);
220 for (
size_t i = 0;
i < nCommandLineArgs; ++
i)
222 OUString aCommandLineArg;
223 rtl_getAppCommandArg(i, &aCommandLineArg.pData);
224 createStr(aCommandLineArg, pArgs, 7 + i);
227 pArgs[nArgs - 1] =
nullptr;
241 update_file aUpdateFile;
247 OUString aFromBuildID;
248 OUString aSeeAlsoURL;
251 update_file aUpdateFile;
252 std::vector<language_file> aLanguageFiles;
255bool isUserWritable(
const OUString& rFileURL)
257 osl::FileStatus aStatus(osl_FileStatus_Mask_Attributes);
258 osl::DirectoryItem aDirectoryItem;
260 osl::FileBase::RC eRes = osl::DirectoryItem::get(rFileURL, aDirectoryItem);
261 if (eRes != osl::FileBase::E_None)
263 Updater::log(
"Could not get the directory item for: " + rFileURL);
267 osl::FileBase::RC eResult = aDirectoryItem.getFileStatus(aStatus);
268 if (eResult != osl::FileBase::E_None)
270 Updater::log(
"Could not get the file status for: " + rFileURL);
274 bool bReadOnly = (aStatus.getAttributes() &
static_cast<sal_uInt64
>(osl_File_Attribute_ReadOnly)) != 0;
277 Updater::log(
"Update location as determined by: " + rFileURL +
" is read-only.");
289 OUString aTempDirURL = aTempDir.
GetURL();
292 OUString aUpdaterPath = getPathFromURL(aTempDirURL +
"/" + OUString::fromUtf8(pUpdaterName));
295 CharT** pArgs = createCommandLine();
297 bool bSuccess =
true;
298 const char* pUpdaterTestReplace = std::getenv(
"LIBO_UPDATER_TEST_REPLACE");
299 if (!pUpdaterTestReplace)
303 if (execv(aPath.getStr(), pArgs))
305 printf(
"execv failed with error %d %s\n",errno,strerror(errno));
309 bSuccess =
WinLaunchChild((
wchar_t*)aUpdaterPath.getStr(), 8, pArgs);
314 SAL_WARN(
"desktop.updater",
"Updater executable path: " << aUpdaterPath);
315 for (
size_t i = 0;
i < 8 + rtl_getAppCommandArgCount(); ++
i)
322 for (
size_t i = 0;
i < 8 + rtl_getAppCommandArgCount(); ++
i)
335 size_t nmemb,
void *userp)
340 std::string* response =
static_cast<std::string *
>(userp);
341 size_t real_size =
size * nmemb;
342 response->append(
static_cast<char *
>(ptr), real_size);
348class invalid_update_info :
public std::exception
352class invalid_hash :
public std::exception
357 invalid_hash(
const OUString& rExpectedHash,
const OUString& rReceivedHash)
360 OUString(
"Invalid hash found.\nExpected: " + rExpectedHash +
";\nReceived: " + rReceivedHash),
361 RTL_TEXTENCODING_UTF8)
366 const char* what() const noexcept
override
368 return maMessage.getStr();
372class invalid_size :
public std::exception
377 invalid_size(
const size_t nExpectedSize,
const size_t nReceivedSize)
380 OUString(
"Invalid file size found.\nExpected: " + OUString::number(nExpectedSize) +
";\nReceived: " + OUString::number(nReceivedSize)),
381 RTL_TEXTENCODING_UTF8)
386 const char* what() const noexcept
override
388 return maMessage.getStr();
394 return OUString::fromUtf8(rStr.c_str());
397update_file parse_update_file(orcus::json::node& rNode)
399 if (rNode.type() != orcus::json::node_t::object)
401 SAL_WARN(
"desktop.updater",
"invalid update or language file entry");
402 throw invalid_update_info();
405 if (rNode.child_count() < 4)
407 SAL_WARN(
"desktop.updater",
"invalid update or language file entry");
408 throw invalid_update_info();
411 orcus::json::node aURLNode = rNode.child(
"url");
412 orcus::json::node aHashNode = rNode.child(
"hash");
413 orcus::json::node aHashTypeNode = rNode.child(
"hash_function");
414 orcus::json::node aSizeNode = rNode.child(
"size");
416 if (aHashTypeNode.string_value() !=
"sha512")
418 SAL_WARN(
"desktop.updater",
"invalid hash type");
419 throw invalid_update_info();
422 update_file aUpdateFile;
423 aUpdateFile.aURL =
toOUString(aURLNode.string_value().str());
425 if (aUpdateFile.aURL.isEmpty())
426 throw invalid_update_info();
428 aUpdateFile.aHash =
toOUString(aHashNode.string_value().str());
429 aUpdateFile.nSize =
static_cast<sal_uInt32
>(aSizeNode.numeric_value());
433update_info parse_response(
const std::string& rResponse)
435 orcus::json::document_tree aJsonDoc;
436 orcus::json_config aConfig;
437 aJsonDoc.load(rResponse, aConfig);
439 auto aDocumentRoot = aJsonDoc.get_document_root();
440 if (aDocumentRoot.type() != orcus::json::node_t::object)
442 SAL_WARN(
"desktop.updater",
"invalid root entries: " << rResponse);
443 throw invalid_update_info();
446 auto aRootKeys = aDocumentRoot.keys();
447 if (std::find(aRootKeys.begin(), aRootKeys.end(),
"error") != aRootKeys.end())
449 throw invalid_update_info();
451 else if (std::find(aRootKeys.begin(), aRootKeys.end(),
"response") != aRootKeys.end())
453 update_info aUpdateInfo;
454 auto aMsgNode = aDocumentRoot.child(
"response");
455 aUpdateInfo.aMessage =
toOUString(aMsgNode.string_value().str());
459 orcus::json::node aFromNode = aDocumentRoot.child(
"from");
460 if (aFromNode.type() != orcus::json::node_t::string)
462 throw invalid_update_info();
465 orcus::json::node aSeeAlsoNode = aDocumentRoot.child(
"see also");
466 if (aSeeAlsoNode.type() != orcus::json::node_t::string)
468 throw invalid_update_info();
471 orcus::json::node aUpdateNode = aDocumentRoot.child(
"update");
472 if (aUpdateNode.type() != orcus::json::node_t::object)
474 throw invalid_update_info();
477 orcus::json::node aLanguageNode = aDocumentRoot.child(
"languages");
478 if (aUpdateNode.type() != orcus::json::node_t::object)
480 throw invalid_update_info();
483 update_info aUpdateInfo;
484 aUpdateInfo.aFromBuildID =
toOUString(aFromNode.string_value().str());
485 aUpdateInfo.aSeeAlsoURL =
toOUString(aSeeAlsoNode.string_value().str());
487 aUpdateInfo.aUpdateFile = parse_update_file(aUpdateNode);
489 std::vector<orcus::pstring> aLanguages = aLanguageNode.keys();
490 for (
auto const& language : aLanguages)
492 language_file aLanguageFile;
493 auto aLangEntry = aLanguageNode.child(language);
494 aLanguageFile.aLangCode =
toOUString(language.str());
495 aLanguageFile.aUpdateFile = parse_update_file(aLangEntry);
496 aUpdateInfo.aLanguageFiles.push_back(aLanguageFile);
515 auto final_hash = maHash.
finalize();
516 std::stringstream aStrm;
517 for (
auto& i: final_hash)
519 aStrm << std::setw(2) << std::setfill(
'0') << std::hex << (
int)i;
527size_t WriteCallbackFile(
void *ptr,
size_t size,
528 size_t nmemb,
void *userp)
533 WriteDataFile* response =
static_cast<WriteDataFile *
>(userp);
534 size_t real_size =
size * nmemb;
535 response->mpStream->WriteBytes(ptr, real_size);
536 response->maHash.update(
static_cast<const unsigned char*
>(ptr), real_size);
540std::string download_content(
const OString& rURL,
bool bFile, OUString& rHash)
543 std::unique_ptr<CURL, std::function<void(CURL *)>> curl(
544 curl_easy_init(), [](CURL * p) { curl_easy_cleanup(p); });
547 return std::string();
549 curl_easy_setopt(curl.get(), CURLOPT_URL, rURL.getStr());
550 curl_easy_setopt(curl.get(), CURLOPT_USERAGENT,
kUserAgent);
551 bool bUseProxy =
false;
560 char buf[] =
"Expect:";
561 curl_slist* headerlist =
nullptr;
562 headerlist = curl_slist_append(headerlist, buf);
563 curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, headerlist);
564 curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1);
566#if (LIBCURL_VERSION_MAJOR > 7) || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 85)
567 curl_easy_setopt(curl.get(), CURLOPT_REDIR_PROTOCOLS_STR,
"https");
569 curl_easy_setopt(curl.get(), CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
572 std::string response_body;
574 WriteDataFile aFile(aTempFile.
GetStream(StreamMode::WRITE));
577 curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION,
WriteCallback);
578 curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA,
579 static_cast<void *
>(&response_body));
585 OUString aTempFileURL = aTempFile.
GetURL();
586 OString aTempFileURLOString =
OUStringToOString(aTempFileURL, RTL_TEXTENCODING_UTF8);
587 response_body.append(aTempFileURLOString.getStr(), aTempFileURLOString.getLength());
591 curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallbackFile);
592 curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA,
593 static_cast<void *
>(&aFile));
597 curl_easy_setopt(curl.get(), CURLOPT_FAILONERROR, 1);
599 CURLcode cc = curl_easy_perform(curl.get());
601 curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &http_code);
602 if (http_code != 200)
604 SAL_WARN(
"desktop.updater",
"download did not succeed. Error code: " << http_code);
605 throw error_updater(
"download did not succeed");
610 SAL_WARN(
"desktop.updater",
"curl error: " << cc);
611 throw error_updater(
"curl error");
615 rHash = aFile.getHash();
617 return response_body;
620void handle_file_error(osl::FileBase::RC eError,
const OUString& rMsg)
624 case osl::FileBase::E_None:
627 SAL_WARN(
"desktop.updater",
"file error code: " << eError <<
", " << rMsg);
632void download_file(
const OUString& rURL,
size_t nFileSize,
const OUString& rHash,
const OUString& aFileName)
634 Updater::log(
"Download File: " + rURL +
"; FileName: " + aFileName);
637 std::string temp_file = download_content(
aURL,
true, aHash);
638 if (temp_file.empty())
639 throw error_updater(
"empty temp file string");
641 OUString aTempFile = OUString::fromUtf8(temp_file.c_str());
643 osl::File aDownloadedFile(aTempFile);
644 osl::FileBase::RC eError = aDownloadedFile.open(1);
645 handle_file_error(eError,
"Could not open the download file: " + aTempFile);
647 sal_uInt64 nSize = 0;
648 eError = aDownloadedFile.getSize(nSize);
649 handle_file_error(eError,
"Could not get the file size of the downloaded file: " + aTempFile);
650 if (nSize != nFileSize)
652 SAL_WARN(
"desktop.updater",
"File sizes don't match. File might be corrupted.");
653 throw invalid_size(nFileSize, nSize);
658 SAL_WARN(
"desktop.updater",
"File hash don't match. File might be corrupted.");
659 throw invalid_hash(rHash, aHash);
662 OUString aPatchDirURL(
"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
"/" SAL_CONFIGFILE(
"bootstrap")
":UserInstallation}/patch/");
663 rtl::Bootstrap::expandMacros(aPatchDirURL);
666 OUString aDestFile = aPatchDirURL + aFileName;
668 aDownloadedFile.close();
669 eError = osl::File::move(aTempFile, aDestFile);
670 handle_file_error(eError,
"Could not move the file from the Temp directory to the user config: TempFile: " + aTempFile +
"; DestFile: " + aDestFile);
677 OUString aBrandBaseDir(
"${BRAND_BASE_DIR}");
678 rtl::Bootstrap::expandMacros(aBrandBaseDir);
679 bool bUserWritable = isUserWritable(aBrandBaseDir);
682 Updater::log(
"Can't update as the update location is not user writable");
686 OUString aDownloadCheckBaseURL = officecfg::Office::Update::Update::URL::get();
687 static const char* pDownloadCheckBaseURLEnv = std::getenv(
"LIBO_UPDATER_URL");
688 if (pDownloadCheckBaseURLEnv)
690 aDownloadCheckBaseURL = OUString::createFromAscii(pDownloadCheckBaseURLEnv);
696 static const char* pBuildIdEnv = std::getenv(
"LIBO_UPDATER_BUILD");
699 aBuildID = OUString::createFromAscii(pBuildIdEnv);
702 OUString aBuildTarget =
"${_OS}_${_ARCH}";
703 rtl::Bootstrap::expandMacros(aBuildTarget);
705 static const char* pUpdateChannelEnv = std::getenv(
"LIBO_UPDATER_CHANNEL");
706 if (pUpdateChannelEnv)
708 aChannel = OUString::createFromAscii(pUpdateChannelEnv);
711 OUString aDownloadCheckURL = aDownloadCheckBaseURL +
"update/check/1/" + aProductName +
712 "/" + aBuildID +
"/" + aBuildTarget +
"/" + aChannel;
719 std::string response_body = download_content(
aURL,
false, aHash);
720 if (!response_body.empty())
723 update_info aUpdateInfo = parse_response(response_body);
724 if (aUpdateInfo.aUpdateFile.aURL.isEmpty())
728 SAL_WARN(
"desktop.updater",
"Message received from the updater: " << aUpdateInfo.aMessage);
729 Updater::log(
"Server response: " + aUpdateInfo.aMessage);
733 css::uno::Sequence<OUString> aInstalledLanguages(officecfg::Setup::Office::InstalledLocales::get()->getElementNames());
734 std::set<OUString> aInstalledLanguageSet(std::begin(aInstalledLanguages), std::end(aInstalledLanguages));
735 download_file(aUpdateInfo.aUpdateFile.aURL, aUpdateInfo.aUpdateFile.nSize, aUpdateInfo.aUpdateFile.aHash,
"update.mar");
736 for (
auto& lang_update : aUpdateInfo.aLanguageFiles)
739 if (aInstalledLanguageSet.find(lang_update.aLangCode) != aInstalledLanguageSet.end())
741 OUString aFileName =
"update_" + lang_update.aLangCode +
".mar";
742 download_file(lang_update.aUpdateFile.aURL, lang_update.aUpdateFile.nSize, lang_update.aUpdateFile.aHash, aFileName);
745 OUString aSeeAlsoURL = aUpdateInfo.aSeeAlsoURL;
746 std::shared_ptr< comphelper::ConfigurationChanges > batch(
748 officecfg::Office::Update::Update::SeeAlso::set(aSeeAlsoURL, batch);
753 catch (
const invalid_update_info&)
755 SAL_WARN(
"desktop.updater",
"invalid update information");
758 catch (
const error_updater& e)
760 SAL_WARN(
"desktop.updater",
"error during the update check: " << e.what());
761 Updater::log(OString(
"warning: error by the updater") + e.what());
763 catch (
const invalid_size& e)
765 SAL_WARN(
"desktop.updater", e.what());
768 catch (
const invalid_hash& e)
770 SAL_WARN(
"desktop.updater", e.what());
775 SAL_WARN(
"desktop.updater",
"unknown error during the update check");
782 OUString aUpdateInfoURL(
"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
"/" SAL_CONFIGFILE(
"bootstrap")
":UserInstallation}/patch/updating.log");
783 rtl::Bootstrap::expandMacros(aUpdateInfoURL);
785 return aUpdateInfoURL;
790 OUString aPatchDirURL(
"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
"/" SAL_CONFIGFILE(
"bootstrap")
":UserInstallation}/patch/");
791 rtl::Bootstrap::expandMacros(aPatchDirURL);
803 OUString aInstallDir(
"$BRAND_BASE_DIR/");
804 rtl::Bootstrap::expandMacros(aInstallDir);
806 return getPathFromURL(aInstallDir);
811 OUString aExeDir(
"$BRAND_BASE_DIR/" LIBO_BIN_FOLDER
"/" );
812 rtl::Bootstrap::expandMacros(aExeDir);
819 SAL_INFO(
"desktop.updater", rMessage);
821 SvFileStream aLog(aUpdateLog, StreamMode::STD_READWRITE);
828 SAL_INFO(
"desktop.updater", rMessage);
830 SvFileStream aLog(aUpdateLog, StreamMode::STD_READWRITE);
837 SAL_INFO(
"desktop.updater", pMessage);
839 SvFileStream aLog(aUpdateLog, StreamMode::STD_READWRITE);
846 OUString aBuildID(
"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
"/" SAL_CONFIGFILE(
"version")
":buildid}");
847 rtl::Bootstrap::expandMacros(aBuildID);
854 OUString aUpdateChannel(
"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
"/" SAL_CONFIGFILE(
"version")
":UpdateChannel}");
855 rtl::Bootstrap::expandMacros(aUpdateChannel);
857 return aUpdateChannel;
866 osl::Directory aDir(aPatchDirURL);
869 osl::FileBase::RC eRC;
872 osl::DirectoryItem aItem;
873 eRC = aDir.getNextItem(aItem);
874 if (eRC == osl::FileBase::E_None)
876 osl::FileStatus aStatus(osl_FileStatus_Mask_All);
877 if (aItem.getFileStatus(aStatus) != osl::FileBase::E_None)
880 if (!aStatus.isRegular())
883 OUString
aURL = aStatus.getFileURL();
884 if (!
aURL.endsWith(
".mar"))
888 osl::File::remove(
aURL);
891 while (eRC == osl::FileBase::E_None);
SvStream & WriteOString(std::string_view rStr)
bool WriteLine(std::string_view rStr)
sal_uInt64 Seek(sal_uInt64 nPos)
sal_uInt64 remainingSize()
static void removeUpdateFiles()
static void log(const OUString &rMessage)
static OUString getUpdateChannel()
static OUString getExecutableDirURL()
static OUString getPatchDirURL()
static OUString getUpdateFileURL()
static OUString getInstallationPath()
static OUString getUpdateInfoLog()
static OUString getBuildID()
static std::shared_ptr< ConfigurationChanges > create()
std::vector< unsigned char > finalize()
static OUString getProductName()
void EnableKillingFile(bool bEnable=true)
SvStream * GetStream(StreamMode eMode)
OUString const & GetURL() const
#define SAL_CONFIGFILE(name)
#define SAL_WARN(area, stream)
#define SAL_INFO(area, stream)
static size_t WriteCallback(void const *ptr, size_t size, size_t nmemb, void *userp)
css::uno::Reference< css::deployment::XPackageRegistry > create(css::uno::Reference< css::deployment::XPackageRegistry > const &xRootRegistry, OUString const &context, OUString const &cachePath, css::uno::Reference< css::uno::XComponentContext > const &xComponentContext)
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
const wchar_t *typedef int(__stdcall *DllNativeUnregProc)(int
BOOL WinLaunchChild(const wchar_t *exePath, int argc, wchar_t **argv, HANDLE userToken, HANDLE *hProcess)