diff options
-rw-r--r-- | desktop/source/app/app.cxx | 2 | ||||
-rw-r--r-- | distro-configs/LibreOfficeFlatpak.conf | 1 | ||||
-rw-r--r-- | include/sfx2/sfxhelp.hxx | 2 | ||||
-rw-r--r-- | sfx2/source/appl/sfxhelp.cxx | 258 | ||||
-rw-r--r-- | solenv/flatpak-manifest.in | 8 |
5 files changed, 268 insertions, 3 deletions
diff --git a/desktop/source/app/app.cxx b/desktop/source/app/app.cxx index 1d5554a49ce8..73bcb591fcd7 100644 --- a/desktop/source/app/app.cxx +++ b/desktop/source/app/app.cxx @@ -109,6 +109,7 @@ #include <vcl/help.hxx> #include <vcl/weld.hxx> #include <vcl/settings.hxx> +#include <sfx2/sfxhelp.hxx> #include <sfx2/sfxsids.hrc> #include <sfx2/app.hxx> #include <sfx2/safemode.hxx> @@ -1694,6 +1695,7 @@ int Desktop::doShutdown() // remove temp directory RemoveTemporaryDirectory(); + SfxHelp::removeFlatpakHelpTemporaryDirectory(); // flush evtl. configuration changes so that all config files in user // dir are written diff --git a/distro-configs/LibreOfficeFlatpak.conf b/distro-configs/LibreOfficeFlatpak.conf index be8689996c85..c0c79b7ce959 100644 --- a/distro-configs/LibreOfficeFlatpak.conf +++ b/distro-configs/LibreOfficeFlatpak.conf @@ -3,6 +3,7 @@ --enable-release-build --with-ant-home=/run/build/libreoffice/ant --with-extra-buildid=Flatpak +--with-help=html --with-jdk-home=/usr/lib/sdk/openjdk10/jvm/openjdk-10 --with-lang=ALL --with-system-libs diff --git a/include/sfx2/sfxhelp.hxx b/include/sfx2/sfxhelp.hxx index 99ca1a062f04..d7afeb2b9af0 100644 --- a/include/sfx2/sfxhelp.hxx +++ b/include/sfx2/sfxhelp.hxx @@ -53,6 +53,8 @@ public: static OUString GetCurrentModuleIdentifier(); // Check for built-in help static bool IsHelpInstalled(); + + static void removeFlatpakHelpTemporaryDirectory(); }; #endif // INCLUDED_SFX2_SFXHELP_HXX diff --git a/sfx2/source/appl/sfxhelp.cxx b/sfx2/source/appl/sfxhelp.cxx index 32bdcab980f8..38ba4fd9ef5e 100644 --- a/sfx2/source/appl/sfxhelp.cxx +++ b/sfx2/source/appl/sfxhelp.cxx @@ -22,6 +22,8 @@ #include <set> #include <algorithm> +#include <cassert> + #include <sal/log.hxx> #include <com/sun/star/uno/Reference.h> #include <com/sun/star/frame/Desktop.hpp> @@ -47,12 +49,14 @@ #include <tools/urlobj.hxx> #include <ucbhelper/content.hxx> #include <unotools/pathoptions.hxx> +#include <rtl/byteseq.hxx> #include <rtl/ustring.hxx> #include <officecfg/Office/Common.hxx> #include <osl/process.h> #include <osl/file.hxx> #include <unotools/bootstrap.hxx> #include <unotools/tempfile.hxx> +#include <unotools/ucbhelper.hxx> #include <rtl/uri.hxx> #include <vcl/commandinfoprovider.hxx> #include <vcl/layout.hxx> @@ -713,6 +717,232 @@ static bool impl_showOnlineHelp( const OUString& rURL ) return false; } +namespace { + +bool isFlatpak() { + static auto const flatpak = [] { return std::getenv("LIBO_FLATPAK") != nullptr; }(); + return flatpak; +} + +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 correpsonding 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=<path> + // app-extensions=...;org.libreoffice.LibreOffice.Help=<sha>;... + // 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(); + } + o3tl::string_view const line( + reinterpret_cast<char const *>(bytes.getConstArray()), bytes.getLength()); + if (instance) { + static constexpr auto keyPath = OUStringLiteral("app-path="); + static constexpr auto keyExtensions = OUStringLiteral("app-extensions="); + if (!havePath && line.length() >= unsigned(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() >= unsigned(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 <sha> from ...;org.libreoffice.LibreOffice.Help=<sha>;...: + OUString sha; + for (sal_Int32 i = 0;;) { + OUString elem; + 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 <path> is of the form + // /.../app/org.libreoffice.LibreOffice/<arch>/<branch>/<sha'>/files + // rewrite it as + // /.../runtime/org.libreoffice.LibreOffice.Help/<arch>/<branch>/<sha>/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 auto segments = OUStringLiteral("/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.size; + 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.copy(0, i1) + "/runtime/org.libreoffice.LibreOffice.Help/" + + path.copy(i2, i3 - i2) + sha + path.copy(i4); + // Turn <path> 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; + } +} + +// Must only be accessed with SolarMutex locked: +static struct { + bool created = false; + OUString url; +} flatpakHelpTemporaryDirectoryStatus; + +bool createFlatpakHelpTemporaryDirectory(OUString ** url) { + assert(url != nullptr); + DBG_TESTSOLARMUTEX(); + if (!flatpakHelpTemporaryDirectoryStatus.created) { + auto const env = std::getenv("XDG_CACHE_HOME"); + if (env == nullptr) { + SAL_WARN("sfx.appl", "LIBO_FLATPAK mode but unset XDG_CACHE_HOME"); + return false; + } + OUString path; + if (!rtl_convertStringToUString( + &path.pData, env, std::strlen(env), 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 XDG_CACHE_HOME \"" << env << "\" encoding"); + return false; + } + OUString parent; + auto const err = osl::FileBase::getFileURLFromSystemPath(path, parent); + if (err != osl::FileBase::E_None) { + SAL_WARN( + "sfx.appl", + "LIBO_FLATPAK mode failure converting XDG_CACHE_HOME \"" << path << "\" to URL: " + << err); + return false; + } + if (!parent.endsWith("/")) { + parent += "/"; + } + auto const tmp = utl::TempFile(&parent, true); + if (!tmp.IsValid()) { + SAL_WARN( + "sfx.appl", "LIBO_FLATPAK mode failure creating temp dir at <" << parent << ">"); + return false; + } + flatpakHelpTemporaryDirectoryStatus.url = tmp.GetURL(); + flatpakHelpTemporaryDirectoryStatus.created = true; + } + *url = &flatpakHelpTemporaryDirectoryStatus.url; + return true; +} + +} + #define SHTML1 "<!DOCTYPE HTML><html lang=\"en-US\"><head><meta charset=\"UTF-8\">" #define SHTML2 "<meta http-equiv=\"refresh\" content=\"1\" url=\"" #define SHTML3 "\"><script type=\"text/javascript\"> window.location.href = \"" @@ -720,16 +950,26 @@ static bool impl_showOnlineHelp( const OUString& rURL ) static bool impl_showOfflineHelp( const OUString& rURL ) { - const OUString& aBaseInstallPath = getHelpRootURL(); + OUString aBaseInstallPath = getHelpRootURL(); + // For the flatpak case, find the pathname outside the flatpak sandbox that corresponds to + // aBaseInstallPath, because that is what needs to be stored in aTempFile below: + if (isFlatpak() && !rewriteFlatpakHelpRootUrl(&aBaseInstallPath)) { + return false; + } OUString aHelpLink( aBaseInstallPath + "/index.html?" ); OUString aTarget = "Target=" + rURL.copy(RTL_CONSTASCII_LENGTH("vnd.sun.star.help://")); aTarget = aTarget.replaceAll("%2F","/").replaceAll("?","&"); aHelpLink += aTarget; - // get a html tempfile + // Get a html tempfile (for the flatpak case, create it in XDG_CACHE_HOME instead of /tmp for + // technical reasons, so that it can be accessed by the browser running outside the sandbox): OUString const aExtension(".html"); - ::utl::TempFile aTempFile("NewHelp", true, &aExtension, nullptr, false ); + OUString * parent = nullptr; + if (isFlatpak() && !createFlatpakHelpTemporaryDirectory(&parent)) { + return false; + } + ::utl::TempFile aTempFile("NewHelp", true, &aExtension, parent, false ); SvStream* pStream = aTempFile.GetStream(StreamMode::WRITE); pStream->SetStreamCharSet(RTL_TEXTENCODING_UTF8); @@ -1118,4 +1358,16 @@ bool SfxHelp::IsHelpInstalled() return impl_hasHelpInstalled(); } +void SfxHelp::removeFlatpakHelpTemporaryDirectory() { + DBG_TESTSOLARMUTEX(); + if (flatpakHelpTemporaryDirectoryStatus.created) { + if (!utl::UCBContentHelper::Kill(flatpakHelpTemporaryDirectoryStatus.url)) { + SAL_INFO( + "sfx.appl", + "LIBO_FLATPAK mode failure removing directory <" + << flatpakHelpTemporaryDirectoryStatus.url << ">"); + } + } +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/solenv/flatpak-manifest.in b/solenv/flatpak-manifest.in index 8ab750dffddd..1454c089b8e9 100644 --- a/solenv/flatpak-manifest.in +++ b/solenv/flatpak-manifest.in @@ -570,6 +570,14 @@ ] } ], + "add-extensions": { + "org.libreoffice.LibreOffice.Help": { + "directory": "libreoffice/help", + "bundle": true, + "autodelete": true, + "no-autodownload": true + } + }, "finish-args": [ "--share=network", "--share=ipc", |