/* -*- 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/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #ifdef MACOSX #include #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "newhelp.hxx" #include #include #include #include #include #include #include #include using namespace ::com::sun::star::beans; using namespace ::com::sun::star::frame; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::util; using namespace ::com::sun::star::lang; namespace { class NoHelpErrorBox { private: std::unique_ptr m_xErrBox; public: DECL_STATIC_LINK(NoHelpErrorBox, HelpRequestHdl, weld::Widget&, bool); public: explicit NoHelpErrorBox(weld::Widget* pParent) : m_xErrBox(Application::CreateMessageDialog(pParent, VclMessageType::Error, VclButtonsType::Ok, SfxResId(RID_STR_HLPFILENOTEXIST))) { // Error message: "No help available" m_xErrBox->connect_help(LINK(nullptr, NoHelpErrorBox, HelpRequestHdl)); } void run() { m_xErrBox->run(); } }; } IMPL_STATIC_LINK_NOARG(NoHelpErrorBox, HelpRequestHdl, weld::Widget&, bool) { // do nothing, because no help available return false; } static OUString const & HelpLocaleString(); namespace { /// Root path of the help. OUString const & getHelpRootURL() { static OUString const s_instURL = []() { OUString tmp = officecfg::Office::Common::Path::Current::Help::get(); if (tmp.isEmpty()) { // try to determine path from default tmp = "$(instpath)/" LIBO_SHARE_HELP_FOLDER; } // replace anything like $(instpath); SvtPathOptions aOptions; tmp = aOptions.SubstituteVariable(tmp); OUString url; if (osl::FileBase::getFileURLFromSystemPath(tmp, url) == osl::FileBase::E_None) tmp = url; return tmp; }(); return s_instURL; } bool impl_checkHelpLocalePath(OUString const & rpPath) { osl::DirectoryItem directoryItem; bool bOK = false; osl::FileStatus fileStatus(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL | osl_FileStatus_Mask_FileName); if (osl::DirectoryItem::get(rpPath, directoryItem) == osl::FileBase::E_None && directoryItem.getFileStatus(fileStatus) == osl::FileBase::E_None && fileStatus.isDirectory()) { bOK = true; } return bOK; } /// Check for built-in help /// Check if help//err.html file exist bool impl_hasHelpInstalled() { if (comphelper::LibreOfficeKit::isActive()) return false; // detect installed locale static OUString const aLocaleStr = HelpLocaleString(); OUString helpRootURL = getHelpRootURL() + "/" + aLocaleStr + "/err.html"; bool bOK = false; osl::DirectoryItem directoryItem; if(osl::DirectoryItem::get(helpRootURL, directoryItem) == osl::FileBase::E_None){ bOK=true; } SAL_INFO( "sfx.appl", "Checking old help installed " << bOK); return bOK; } /// Check for html built-in help /// Check if help/lang/text folder exist. Only html has it. bool impl_hasHTMLHelpInstalled() { if (comphelper::LibreOfficeKit::isActive()) return false; // detect installed locale static OUString const aLocaleStr = HelpLocaleString(); OUString helpRootURL = getHelpRootURL() + "/" + aLocaleStr + "/text"; bool bOK = impl_checkHelpLocalePath( helpRootURL ); SAL_INFO( "sfx.appl", "Checking new help (html) installed " << bOK); return bOK; } } // namespace /// Return the locale we prefer for displaying help static OUString const & HelpLocaleString() { if (comphelper::LibreOfficeKit::isActive()) return comphelper::LibreOfficeKit::getLanguageTag().getBcp47(); static OUString aLocaleStr; if (!aLocaleStr.isEmpty()) return aLocaleStr; static const OUStringLiteral aEnglish(u"en-US"); // detect installed locale aLocaleStr = utl::ConfigManager::getUILocale(); if ( aLocaleStr.isEmpty() ) { aLocaleStr = aEnglish; return aLocaleStr; } // get fall-back language (country) OUString sLang = aLocaleStr; sal_Int32 nSepPos = sLang.indexOf( '-' ); if (nSepPos != -1) { sLang = sLang.copy( 0, nSepPos ); } OUString sHelpPath(""); sHelpPath = getHelpRootURL() + "/" + utl::ConfigManager::getProductVersion() + "/" + aLocaleStr; if (impl_checkHelpLocalePath(sHelpPath)) { return aLocaleStr; } sHelpPath = getHelpRootURL() + "/" + utl::ConfigManager::getProductVersion() + "/" + sLang; if (impl_checkHelpLocalePath(sHelpPath)) { aLocaleStr = sLang; return aLocaleStr; } sHelpPath = getHelpRootURL() + "/" + aLocaleStr; if (impl_checkHelpLocalePath(sHelpPath)) { return aLocaleStr; } sHelpPath = getHelpRootURL() + "/" + sLang; if (impl_checkHelpLocalePath(sHelpPath)) { aLocaleStr = sLang; return aLocaleStr; } sHelpPath = getHelpRootURL() + "/" + utl::ConfigManager::getProductVersion() + "/" + aEnglish; if (impl_checkHelpLocalePath(sHelpPath)) { aLocaleStr = aEnglish; return aLocaleStr; } sHelpPath = getHelpRootURL() + "/" + aEnglish; if (impl_checkHelpLocalePath(sHelpPath)) { aLocaleStr = aEnglish; return aLocaleStr; } return aLocaleStr; } void AppendConfigToken( OUStringBuffer& rURL, bool bQuestionMark ) { OUString aLocaleStr = HelpLocaleString(); // query part exists? if ( bQuestionMark ) // no, so start with '?' rURL.append('?'); else // yes, so only append with '&' rURL.append('&'); // set parameters rURL.append("Language="); rURL.append(aLocaleStr); rURL.append("&System="); rURL.append(officecfg::Office::Common::Help::System::get()); rURL.append("&Version="); rURL.append(utl::ConfigManager::getProductVersion()); } static bool GetHelpAnchor_Impl( std::u16string_view _rURL, OUString& _rAnchor ) { bool bRet = false; try { ::ucbhelper::Content aCnt( INetURLObject( _rURL ).GetMainURL( INetURLObject::DecodeMechanism::NONE ), Reference< css::ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() ); OUString sAnchor; if ( aCnt.getPropertyValue("AnchorName") >>= sAnchor ) { if ( !sAnchor.isEmpty() ) { _rAnchor = sAnchor; bRet = true; } } else { SAL_WARN( "sfx.appl", "Property 'AnchorName' is missing" ); } } catch (const css::uno::Exception&) { } return bRet; } namespace { class SfxHelp_Impl { public: static OUString GetHelpText( const OUString& aCommandURL, const OUString& rModule ); }; } OUString SfxHelp_Impl::GetHelpText( const OUString& aCommandURL, const OUString& rModule ) { // create help url OUStringBuffer aHelpURL( SfxHelp::CreateHelpURL( aCommandURL, rModule ) ); // added 'active' parameter sal_Int32 nIndex = aHelpURL.lastIndexOf( '#' ); if ( nIndex < 0 ) nIndex = aHelpURL.getLength(); aHelpURL.insert( nIndex, "&Active=true" ); // load help string return SfxContentHelper::GetActiveHelpString( aHelpURL.makeStringAndClear() ); } SfxHelp::SfxHelp() : bIsDebug(false) , bLaunchingHelp(false) { // read the environment variable "HELP_DEBUG" // if it's set, you will see debug output on active help OUString sHelpDebug; OUString sEnvVarName( "HELP_DEBUG" ); osl_getEnvironment( sEnvVarName.pData, &sHelpDebug.pData ); bIsDebug = !sHelpDebug.isEmpty(); } SfxHelp::~SfxHelp() { } static OUString getDefaultModule_Impl() { OUString sDefaultModule; SvtModuleOptions aModOpt; if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) ) sDefaultModule = "swriter"; else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) ) sDefaultModule = "scalc"; else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) ) sDefaultModule = "simpress"; else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) ) sDefaultModule = "sdraw"; else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::MATH ) ) sDefaultModule = "smath"; else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::CHART ) ) sDefaultModule = "schart"; else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::BASIC ) ) sDefaultModule = "sbasic"; else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::DATABASE ) ) sDefaultModule = "sdatabase"; else { SAL_WARN( "sfx.appl", "getDefaultModule_Impl(): no module installed" ); } return sDefaultModule; } static OUString getCurrentModuleIdentifier_Impl() { OUString sIdentifier; Reference < XComponentContext > xContext = ::comphelper::getProcessComponentContext(); Reference < XModuleManager2 > xModuleManager = ModuleManager::create(xContext); Reference < XDesktop2 > xDesktop = Desktop::create(xContext); Reference < XFrame > xCurrentFrame = xDesktop->getCurrentFrame(); if ( xCurrentFrame.is() ) { try { sIdentifier = xModuleManager->identify( xCurrentFrame ); } catch (const css::frame::UnknownModuleException&) { SAL_INFO( "sfx.appl", "SfxHelp::getCurrentModuleIdentifier_Impl(): unknown module (help in help?)" ); } catch (const Exception&) { TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelp::getCurrentModuleIdentifier_Impl(): exception of XModuleManager::identify()" ); } } return sIdentifier; } namespace { OUString MapModuleIdentifier(const OUString &rFactoryShortName) { OUString aFactoryShortName(rFactoryShortName); // Map some module identifiers to their "real" help module string. if ( aFactoryShortName == "chart2" ) aFactoryShortName = "schart" ; else if ( aFactoryShortName == "BasicIDE" ) aFactoryShortName = "sbasic"; else if ( aFactoryShortName == "sweb" || aFactoryShortName == "sglobal" || aFactoryShortName == "swxform" ) aFactoryShortName = "swriter" ; else if ( aFactoryShortName == "dbquery" || aFactoryShortName == "dbbrowser" || aFactoryShortName == "dbrelation" || aFactoryShortName == "dbtable" || aFactoryShortName == "dbapp" || aFactoryShortName == "dbreport" || aFactoryShortName == "dbtdata" || aFactoryShortName == "swreport" || aFactoryShortName == "swform" ) aFactoryShortName = "sdatabase"; else if ( aFactoryShortName == "sbibliography" || aFactoryShortName == "sabpilot" || aFactoryShortName == "scanner" || aFactoryShortName == "spropctrlr" || aFactoryShortName == "StartModule" ) aFactoryShortName.clear(); return aFactoryShortName; } } OUString SfxHelp::GetHelpModuleName_Impl(std::u16string_view rHelpID) { OUString aFactoryShortName; //rhbz#1438876 detect preferred module for this help id, e.g. csv dialog //for calc import before any toplevel is created and so context is //otherwise unknown. Cosmetic, same help is shown in any case because its //in the shared section, but title bar would state "Writer" when context is //expected to be "Calc" std::u16string_view sRemainder; if (o3tl::starts_with(rHelpID, u"modules/", &sRemainder)) { std::size_t nEndModule = sRemainder.find(u'/'); aFactoryShortName = nEndModule != std::u16string_view::npos ? sRemainder.substr(0, nEndModule) : sRemainder; } if (aFactoryShortName.isEmpty()) { OUString aModuleIdentifier = getCurrentModuleIdentifier_Impl(); if (!aModuleIdentifier.isEmpty()) { try { Reference < XModuleManager2 > xModuleManager( ModuleManager::create(::comphelper::getProcessComponentContext()) ); Sequence< PropertyValue > lProps; xModuleManager->getByName( aModuleIdentifier ) >>= lProps; auto pProp = std::find_if(std::cbegin(lProps), std::cend(lProps), [](const PropertyValue& rProp) { return rProp.Name == "ooSetupFactoryShortName"; }); if (pProp != std::cend(lProps)) pProp->Value >>= aFactoryShortName; } catch (const Exception&) { TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelp::GetHelpModuleName_Impl()" ); } } } if (!aFactoryShortName.isEmpty()) aFactoryShortName = MapModuleIdentifier(aFactoryShortName); if (aFactoryShortName.isEmpty()) aFactoryShortName = getDefaultModule_Impl(); return aFactoryShortName; } OUString SfxHelp::CreateHelpURL_Impl( const OUString& aCommandURL, const OUString& rModuleName ) { // build up the help URL OUStringBuffer aHelpURL("vnd.sun.star.help://"); bool bHasAnchor = false; OUString aAnchor; OUString aModuleName( rModuleName ); if (aModuleName.isEmpty()) aModuleName = getDefaultModule_Impl(); aHelpURL.append(aModuleName); if ( aCommandURL.isEmpty() ) aHelpURL.append("/start"); else { aHelpURL.append('/'); aHelpURL.append(rtl::Uri::encode(aCommandURL, rtl_UriCharClassRelSegment, rtl_UriEncodeKeepEscapes, RTL_TEXTENCODING_UTF8)); OUStringBuffer aTempURL = aHelpURL; AppendConfigToken( aTempURL, true ); bHasAnchor = GetHelpAnchor_Impl(aTempURL, aAnchor); } AppendConfigToken( aHelpURL, true ); if ( bHasAnchor ) { aHelpURL.append('#'); aHelpURL.append(aAnchor); } return aHelpURL.makeStringAndClear(); } static SfxHelpWindow_Impl* impl_createHelp(Reference< XFrame2 >& rHelpTask , Reference< XFrame >& rHelpContent) { Reference < XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() ); // otherwise - create new help task Reference< XFrame2 > xHelpTask( xDesktop->findFrame( "OFFICE_HELP_TASK", FrameSearchFlag::TASKS | FrameSearchFlag::CREATE), UNO_QUERY); if (!xHelpTask.is()) return nullptr; // create all internal windows and sub frames ... Reference< css::awt::XWindow > xParentWindow = xHelpTask->getContainerWindow(); VclPtr pParentWindow = VCLUnoHelper::GetWindow( xParentWindow ); VclPtrInstance pHelpWindow( xHelpTask, pParentWindow ); Reference< css::awt::XWindow > xHelpWindow = VCLUnoHelper::GetInterface( pHelpWindow ); Reference< XFrame > xHelpContent; if (xHelpTask->setComponent( xHelpWindow, Reference< XController >() )) { // Customize UI ... xHelpTask->setName("OFFICE_HELP_TASK"); Reference< XPropertySet > xProps(xHelpTask, UNO_QUERY); if (xProps.is()) xProps->setPropertyValue( "Title", Any(SfxResId(STR_HELP_WINDOW_TITLE))); pHelpWindow->setContainerWindow( xParentWindow ); xParentWindow->setVisible(true); xHelpWindow->setVisible(true); // This sub frame is created internally (if we called new SfxHelpWindow_Impl() ...) // It should exist :-) xHelpContent = xHelpTask->findFrame("OFFICE_HELP", FrameSearchFlag::CHILDREN); } if (!xHelpContent.is()) { pHelpWindow.disposeAndClear(); return nullptr; } xHelpContent->setName("OFFICE_HELP"); rHelpTask = xHelpTask; rHelpContent = xHelpContent; return pHelpWindow; } OUString SfxHelp::GetHelpText( const OUString& aCommandURL, const vcl::Window* pWindow ) { OUString sModuleName = GetHelpModuleName_Impl(aCommandURL); auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommandURL, getCurrentModuleIdentifier_Impl()); OUString sRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties); OUString sHelpText = SfxHelp_Impl::GetHelpText( sRealCommand.isEmpty() ? aCommandURL : sRealCommand, sModuleName ); OString aNewHelpId; if (pWindow && sHelpText.isEmpty()) { // no help text found -> try with parent help id. vcl::Window* pParent = pWindow->GetParent(); while ( pParent ) { aNewHelpId = pParent->GetHelpId(); sHelpText = SfxHelp_Impl::GetHelpText( OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8), sModuleName ); if (!sHelpText.isEmpty()) pParent = nullptr; else pParent = pParent->GetParent(); } if (bIsDebug && sHelpText.isEmpty()) aNewHelpId.clear(); } // add some debug information? if ( bIsDebug ) { sHelpText += "\n-------------\n" + sModuleName + ": " + aCommandURL; if ( !aNewHelpId.isEmpty() ) { sHelpText += " - " + OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8); } } return sHelpText; } OUString SfxHelp::GetHelpText(const OUString& aCommandURL, const weld::Widget* pWidget) { OUString sModuleName = GetHelpModuleName_Impl(aCommandURL); auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommandURL, getCurrentModuleIdentifier_Impl()); OUString sRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties); OUString sHelpText = SfxHelp_Impl::GetHelpText( sRealCommand.isEmpty() ? aCommandURL : sRealCommand, sModuleName ); OString aNewHelpId; if (pWidget && sHelpText.isEmpty()) { // no help text found -> try with parent help id. std::unique_ptr xParent(pWidget->weld_parent()); while (xParent) { aNewHelpId = xParent->get_help_id(); sHelpText = SfxHelp_Impl::GetHelpText( OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8), sModuleName ); if (!sHelpText.isEmpty()) xParent.reset(); else xParent = xParent->weld_parent(); } if (bIsDebug && sHelpText.isEmpty()) aNewHelpId.clear(); } // add some debug information? if ( bIsDebug ) { sHelpText += "\n-------------\n" + sModuleName + ": " + aCommandURL; if ( !aNewHelpId.isEmpty() ) { sHelpText += " - " + OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8); } } return sHelpText; } OUString SfxHelp::GetURLHelpText(std::u16string_view aURL) { bool bCtrlClickHlink = SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::CtrlClickHyperlink); // "ctrl-click to follow link:" for not MacOS // "⌘-click to follow link:" for MacOs vcl::KeyCode aCode(KEY_SPACE); vcl::KeyCode aModifiedCode(KEY_SPACE, KEY_MOD1); OUString aModStr(aModifiedCode.GetName()); aModStr = aModStr.replaceFirst(aCode.GetName(), ""); aModStr = aModStr.replaceAll("+", ""); OUString aHelpStr = bCtrlClickHlink ? SfxResId(STR_CTRLCLICKHYPERLINK) : SfxResId(STR_CLICKHYPERLINK); aHelpStr = aHelpStr.replaceFirst("%{key}", aModStr); aHelpStr = aHelpStr.replaceFirst("%{link}", aURL); return aHelpStr; } void SfxHelp::SearchKeyword( const OUString& rKeyword ) { Start_Impl(OUString(), static_cast(nullptr), rKeyword); } bool SfxHelp::Start( const OUString& rURL, const vcl::Window* pWindow ) { if (bLaunchingHelp) return true; bLaunchingHelp = true; bool bRet = Start_Impl( rURL, pWindow ); bLaunchingHelp = false; return bRet; } bool SfxHelp::Start(const OUString& rURL, weld::Widget* pWidget) { if (bLaunchingHelp) return true; bLaunchingHelp = true; bool bRet = Start_Impl(rURL, pWidget, OUString()); bLaunchingHelp = false; return bRet; } /// Redirect the vnd.sun.star.help:// urls to http://help.libreoffice.org static bool impl_showOnlineHelp(const OUString& rURL, weld::Widget* pDialogParent) { static constexpr OUStringLiteral aInternal(u"vnd.sun.star.help://"); if ( rURL.getLength() <= aInternal.getLength() || !rURL.startsWith(aInternal) ) return false; OUString aHelpLink = officecfg::Office::Common::Help::HelpRootURL::get(); OUString aTarget = OUString::Concat("Target=") + rURL.subView(aInternal.getLength()); aTarget = aTarget.replaceAll("%2F", "/").replaceAll("?", "&"); aHelpLink += aTarget; if (comphelper::LibreOfficeKit::isActive()) { if(SfxViewShell* pViewShell = SfxViewShell::Current()) { pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_HYPERLINK_CLICKED, aHelpLink.toUtf8().getStr()); return true; } else if (GetpApp()) { GetpApp()->libreOfficeKitViewCallback(LOK_CALLBACK_HYPERLINK_CLICKED, aHelpLink.toUtf8().getStr()); return true; } return false; } try { #ifdef MACOSX LSOpenCFURLRef(CFURLCreateWithString(kCFAllocatorDefault, CFStringCreateWithCString(kCFAllocatorDefault, aHelpLink.toUtf8().getStr(), kCFStringEncodingUTF8), nullptr), nullptr); (void)pDialogParent; #else sfx2::openUriExternally(aHelpLink, false, pDialogParent); #endif return true; } catch (const Exception&) { } return false; } namespace { bool rewriteFlatpakHelpRootUrl(OUString * helpRootUrl) { assert(helpRootUrl != nullptr); //TODO: this function for now assumes that the passed-in *helpRootUrl references // /app/libreoffice/help (which belongs to the org.libreoffice.LibreOffice.Help // extension); it replaces it with the corresponding file URL as seen outside the flatpak // sandbox: struct Failure: public std::exception {}; try { static auto const url = [] { // From /.flatpak-info [Instance] section, read // app-path= // app-extensions=...;org.libreoffice.LibreOffice.Help=;... // lines: osl::File ini("file:///.flatpak-info"); auto err = ini.open(osl_File_OpenFlag_Read); if (err != osl::FileBase::E_None) { SAL_WARN("sfx.appl", "LIBO_FLATPAK mode failure opening /.flatpak-info: " << err); throw Failure(); } OUString path; OUString extensions; bool havePath = false; bool haveExtensions = false; for (bool instance = false; !(havePath && haveExtensions);) { rtl::ByteSequence bytes; err = ini.readLine(bytes); if (err != osl::FileBase::E_None) { SAL_WARN( "sfx.appl", "LIBO_FLATPAK mode reading /.flatpak-info fails with " << err << " before [Instance] app-path"); throw Failure(); } std::string_view const line( reinterpret_cast(bytes.getConstArray()), bytes.getLength()); if (instance) { static constexpr auto keyPath = std::string_view("app-path="); static constexpr auto keyExtensions = std::string_view("app-extensions="); if (!havePath && line.length() >= keyPath.size() && line.substr(0, keyPath.size()) == keyPath.data()) { auto const value = line.substr(keyPath.size()); if (!rtl_convertStringToUString( &path.pData, value.data(), value.length(), osl_getThreadTextEncoding(), (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR))) { SAL_WARN( "sfx.appl", "LIBO_FLATPAK mode failure converting app-path \"" << value << "\" encoding"); throw Failure(); } havePath = true; } else if (!haveExtensions && line.length() >= keyExtensions.size() && line.substr(0, keyExtensions.size()) == keyExtensions.data()) { auto const value = line.substr(keyExtensions.size()); if (!rtl_convertStringToUString( &extensions.pData, value.data(), value.length(), osl_getThreadTextEncoding(), (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR))) { SAL_WARN( "sfx.appl", "LIBO_FLATPAK mode failure converting app-extensions \"" << value << "\" encoding"); throw Failure(); } haveExtensions = true; } else if (line.length() > 0 && line[0] == '[') { SAL_WARN( "sfx.appl", "LIBO_FLATPAK mode /.flatpak-info lacks [Instance] app-path and" " app-extensions"); throw Failure(); } } else if (line == "[Instance]") { instance = true; } } ini.close(); // Extract from ...;org.libreoffice.LibreOffice.Help=;...: OUString sha; for (sal_Int32 i = 0;;) { OUString elem = extensions.getToken(0, ';', i); if (elem.startsWith("org.libreoffice.LibreOffice.Help=", &sha)) { break; } if (i == -1) { SAL_WARN( "sfx.appl", "LIBO_FLATPAK mode /.flatpak-info [Instance] app-extensions \"" << extensions << "\" org.libreoffice.LibreOffice.Help"); throw Failure(); } } // Assuming that is of the form // /.../app/org.libreoffice.LibreOffice////files // rewrite it as // /.../runtime/org.libreoffice.LibreOffice.Help////files // because the extension's files are stored at a different place than the app's files, // so use this hack until flatpak itself provides a better solution: static constexpr OUStringLiteral segments = u"/app/org.libreoffice.LibreOffice/"; auto const i1 = path.lastIndexOf(segments); // use lastIndexOf instead of indexOf, in case the user-controlled prefix /.../ // happens to contain such segments if (i1 == -1) { SAL_WARN( "sfx.appl", "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path << "\" doesn't contain /app/org.libreoffice.LibreOffice/"); throw Failure(); } auto const i2 = i1 + segments.getLength(); auto i3 = path.indexOf('/', i2); if (i3 == -1) { SAL_WARN( "sfx.appl", "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path << "\" doesn't contain branch segment"); throw Failure(); } i3 = path.indexOf('/', i3 + 1); if (i3 == -1) { SAL_WARN( "sfx.appl", "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path << "\" doesn't contain sha segment"); throw Failure(); } ++i3; auto const i4 = path.indexOf('/', i3); if (i4 == -1) { SAL_WARN( "sfx.appl", "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path << "\" doesn't contain files segment"); throw Failure(); } path = path.subView(0, i1) + OUString::Concat("/runtime/org.libreoffice.LibreOffice.Help/") + path.subView(i2, i3 - i2) + sha + path.subView(i4); // Turn into a file URL: OUString url_; err = osl::FileBase::getFileURLFromSystemPath(path, url_); if (err != osl::FileBase::E_None) { SAL_WARN( "sfx.appl", "LIBO_FLATPAK mode failure converting app-path \"" << path << "\" to URL: " << err); throw Failure(); } return url_; }(); *helpRootUrl = url; return true; } catch (Failure &) { return false; } } } // add