diff --git a/common/JailUtil.cpp b/common/JailUtil.cpp
index c2e42138dd73..f0ce50bfa8b5 100644
--- a/common/JailUtil.cpp
+++ b/common/JailUtil.cpp
@@ -426,7 +426,9 @@ void setupChildRoot(bool bindMount, const std::string& childRoot, const std::str
{
// Start with a clean slate.
cleanupJails(childRoot);
- createJailPath(childRoot + CHILDROOT_TMP_INCOMING_PATH);
+
+ createJailPath(childRoot + CHILDROOT_TMP_INCOMING_PATH + "/fonts");
+ createJailPath(childRoot + CHILDROOT_TMP_INCOMING_PATH + "/templates");
disableBindMounting(); // Clear to avoid surprises.
diff --git a/coolwsd.xml.in b/coolwsd.xml.in
index d354da1a6f67..98c4d92ddef9 100644
--- a/coolwsd.xml.in
+++ b/coolwsd.xml.in
@@ -323,6 +323,10 @@
+
+
+
+
false
diff --git a/kit/Kit.cpp b/kit/Kit.cpp
index fa551799dda5..311d016ef945 100644
--- a/kit/Kit.cpp
+++ b/kit/Kit.cpp
@@ -3254,6 +3254,10 @@ void lokit_main(
const std::string tmpSubDir = Poco::Path(tempRoot, "cool-" + jailId).toString();
const std::string jailTmpDir = Poco::Path(jailPath, "tmp").toString();
+ const std::string tmpIncoming = Poco::Path(childRoot, JailUtil::CHILDROOT_TMP_INCOMING_PATH).toString();
+ const std::string sharedTemplate = Poco::Path(tmpIncoming, "templates").toString();
+ const std::string loJailDestImpressTemplatePath = Poco::Path(loJailDestPath, "share/template/common/presnt").toString();
+
const std::string sysTemplateSubDir = Poco::Path(tempRoot, "systemplate-" + jailId).toString();
const std::string jailEtcDir = Poco::Path(jailPath, "etc").toString();
@@ -3313,6 +3317,34 @@ void lokit_main(
return false;
}
+ // copy default tempates from 'common' dir to shared templates dir
+ // TODO: maybe I shouldn't copy if whole point to mounting is that we don't require copying.
+ auto defaultTemplates = FileUtil::getDirEntries(loJailDestImpressTemplatePath);
+ for (auto& name : defaultTemplates)
+ {
+ std::string sourcePath = loJailDestImpressTemplatePath;
+ sourcePath.append("/");
+ sourcePath.append(name);
+ std::string destPath = sharedTemplate;
+ destPath.append("/");
+ destPath.append(name);
+ if (!FileUtil::copy(sourcePath, destPath, false, false))
+ {
+ LOG_WRN("Failed to copy default impress template from ["
+ << sourcePath << "] to [" << sharedTemplate << ']');
+ }
+ }
+
+ // mount the shared templates over the lo shared templates' 'common' dir
+ if (!JailUtil::bind(sharedTemplate, loJailDestImpressTemplatePath) ||
+ !JailUtil::remountReadonly(sharedTemplate, loJailDestImpressTemplatePath))
+ {
+ // TODO: actually do this link on failure
+ LOG_WRN("Failed to mount [" << sharedTemplate << "] -> [" << sharedTemplate
+ << "], will link contents");
+ return false;
+ }
+
// tmpdir inside the jail for added sercurity.
Poco::File(tmpSubDir).createDirectories();
LOG_INF("Mounting random temp dir " << tmpSubDir << " -> " << jailTmpDir);
diff --git a/wsd/COOLWSD.cpp b/wsd/COOLWSD.cpp
index 93dae733e0b5..6d37d2cf3c51 100644
--- a/wsd/COOLWSD.cpp
+++ b/wsd/COOLWSD.cpp
@@ -676,6 +676,7 @@ std::string COOLWSD::ServerName;
std::string COOLWSD::FileServerRoot;
std::string COOLWSD::ServiceRoot;
std::string COOLWSD::TmpFontDir;
+std::string COOLWSD::TmpTemplateDir;
std::string COOLWSD::LOKitVersion;
std::string COOLWSD::ConfigFile = COOLWSD_CONFIGDIR "/coolwsd.xml";
std::string COOLWSD::ConfigDir = COOLWSD_CONFIGDIR "/conf.d";
@@ -1346,6 +1347,8 @@ void COOLWSD::innerInitialize(Poco::Util::Application& self)
{ "extra_export_formats.impress_png", "false" },
{ "extra_export_formats.impress_svg", "false" },
{ "extra_export_formats.impress_tiff", "false" },
+ { "remote_template_config.url", ""},
+ { "remote_font_config.url", ""},
};
// Set default values, in case they are missing from the config file.
@@ -3619,7 +3622,8 @@ int COOLWSD::innerMain()
assert(Server && "The COOLWSDServer instance does not exist.");
Server->findClientPort();
- TmpFontDir = ChildRoot + JailUtil::CHILDROOT_TMP_INCOMING_PATH;
+ TmpFontDir = ChildRoot + JailUtil::CHILDROOT_TMP_INCOMING_PATH + "/fonts";
+ TmpTemplateDir = ChildRoot + JailUtil::CHILDROOT_TMP_INCOMING_PATH + "/templates";
// Start the internal prisoner server and spawn forkit,
// which in turn forks first child.
@@ -3679,17 +3683,40 @@ int COOLWSD::innerMain()
LOG_ERR("Log level is set very high to '" << LogLevel << "' this will have a "
"significant performance impact. Do not use this in production.");
- // Start the remote font downloading polling thread.
- std::unique_ptr remoteFontConfigThread;
+ std::string uriConfigKey;
+ const std::string& fontConfigKey = "remote_font_config.url";
+ const std::string& assetConfigKey = "remote_asset_config.url";
+ bool remoteFontDefined = !ConfigUtil::getConfigValue(fontConfigKey, "").empty();
+ bool remoteAssetDefined = !ConfigUtil::getConfigValue(assetConfigKey, "").empty();
+ // Both defined: warn and use assetConfigKey
+ if (remoteFontDefined && remoteAssetDefined)
+ {
+ LOG_WRN("Both remote_font_config.url and remote_asset_config.url are defined, "
+ "remote_asset_config.url is overriden on remote_font_config.url");
+ uriConfigKey = assetConfigKey;
+ }
+ // only font defined: use fontConfigKey
+ else if (remoteFontDefined && !remoteAssetDefined)
+ {
+ uriConfigKey = fontConfigKey;
+ }
+ // only asset defined: use assetConfigKey
+ else if (!remoteFontDefined && remoteAssetDefined)
+ {
+ uriConfigKey = assetConfigKey;
+ }
+
+ // Start the remote asset downloading polling thread.
+ std::unique_ptr remoteAssetConfigThread;
try
{
- // Fetch font settings from server if configured
- remoteFontConfigThread = std::make_unique(config());
- remoteFontConfigThread->start();
+ // Fetch font and/or templates settings from server if configured
+ remoteAssetConfigThread = std::make_unique(config(), uriConfigKey);
+ remoteAssetConfigThread->start();
}
catch (const Poco::Exception&)
{
- LOG_DBG("No remote_font_config");
+ LOG_DBG("No remote_asset_config");
}
#endif
diff --git a/wsd/COOLWSD.hpp b/wsd/COOLWSD.hpp
index bd2a6d38553e..6b2e68ab0faa 100644
--- a/wsd/COOLWSD.hpp
+++ b/wsd/COOLWSD.hpp
@@ -82,6 +82,7 @@ class COOLWSD final : public Poco::Util::ServerApplication,
static std::string FileServerRoot;
static std::string ServiceRoot; ///< There are installations that need prefixing every page with some path.
static std::string TmpFontDir;
+ static std::string TmpTemplateDir;
static std::string LOKitVersion;
static bool EnableTraceEventLogging;
static bool EnableAccessibility;
diff --git a/wsd/RemoteConfig.cpp b/wsd/RemoteConfig.cpp
index 35e24b87748c..4606db4189f6 100644
--- a/wsd/RemoteConfig.cpp
+++ b/wsd/RemoteConfig.cpp
@@ -16,6 +16,8 @@
#include
#include "RemoteConfig.hpp"
+#include "FileUtil.hpp"
+#include "Util.hpp"
#include
#include
@@ -106,7 +108,8 @@ void RemoteJSONPoll::pollingThread()
{
std::string kind;
JsonUtil::findJSONValue(remoteJson, "kind", kind);
- if (kind == _expectedKind)
+ const std::pair expectedKinds = Util::split(_expectedKind, '|');
+ if (kind == expectedKinds.first || kind == expectedKinds.second)
{
handleJSON(remoteJson);
}
@@ -556,105 +559,160 @@ void RemoteConfigPoll::handleOptions(const Poco::JSON::Object::Ptr& remoteJson)
}
}
-void RemoteFontConfigPoll::handleJSON(const Poco::JSON::Object::Ptr& remoteJson)
+bool RemoteAssetConfigPoll::getNewAssets(const Poco::JSON::Object::Ptr& remoteJson,
+ const std::string& assetJsonKey,
+ std::map& assets)
{
- // First mark all fonts we have downloaded previously as "inactive" to be able to check if
- // some font gets deleted from the list in the JSON file.
- for (auto& it : fonts)
+ // First mark all assets we have downloaded previously as "inactive" to be able to check if
+ // some asset gets deleted from the list in the JSON file.
+ for (auto& it : assets)
it.second.active = false;
- // Just pick up new fonts.
- auto fontsPtr = remoteJson->getArray("fonts");
- if (!fontsPtr)
+ bool reDownloadConfig = false;
+ auto assetsPtr = remoteJson->getArray(assetJsonKey);
+ if (!assetsPtr)
{
- LOG_WRN("The 'fonts' property does not exist or is not an array");
- return;
+ LOG_WRN("The [" + assetJsonKey + "] property does not exist or is not an array");
+ return reDownloadConfig;
}
- for (std::size_t i = 0; i < fontsPtr->size(); i++)
+ for (std::size_t i = 0; i < assetsPtr->size(); i++)
{
- if (!fontsPtr->isObject(i))
- LOG_WRN("Element " << i << " in fonts array is not an object");
+ if (!assetsPtr->isObject(i))
+ LOG_WRN("Element " << i << " in " << assetJsonKey << " array is not an object");
else
{
- const auto fontPtr = fontsPtr->getObject(i);
- const auto uriPtr = fontPtr->get("uri");
+ const auto assetPtr = assetsPtr->getObject(i);
+ const auto uriPtr = assetPtr->get("uri");
if (uriPtr.isEmpty() || !uriPtr.isString())
- LOG_WRN("Element in fonts array does not have an 'uri' property or it is not a "
- "string");
+ LOG_WRN("Element in " << assetJsonKey
+ << " array does not have an 'uri' property or it is not a "
+ "string");
else
{
const std::string uri = uriPtr.toString();
- const auto stampPtr = fontPtr->get("stamp");
+ const auto stampPtr = assetPtr->get("stamp");
if (!stampPtr.isEmpty() && !stampPtr.isString())
- LOG_WRN("Element in fonts array with uri '"
- << uri << "' has a stamp property that is not a string, ignored");
- else if (fonts.count(uri) == 0)
+ LOG_WRN("Element in "
+ << assetJsonKey << "array with uri '" << uri
+ << "' has a stamp property that is not a string, ignored");
+ else if (assets.count(uri) == 0)
{
- // First case: This font has not been downloaded.
+ // First case: This asset has not been downloaded.
if (!stampPtr.isEmpty())
{
- if (downloadPlain(uri))
+ if (downloadPlain(uri, assets, assetJsonKey))
{
- fonts[uri].stamp = stampPtr.toString();
- fonts[uri].active = true;
+ assets[uri].stamp = stampPtr.toString();
+ assets[uri].active = true;
}
}
else
{
- if (downloadWithETag(uri, ""))
+ if (downloadWithETag(uri, "", assets, assetJsonKey))
{
- fonts[uri].active = true;
+ assets[uri].active = true;
}
}
}
- else if (!stampPtr.isEmpty() && stampPtr.toString() != fonts[uri].stamp)
+ else if (!stampPtr.isEmpty() && stampPtr.toString() != assets[uri].stamp)
{
- // Second case: Font has been downloaded already, has a "stamp" property,
+ // Second case: asset has been downloaded already, has a "stamp" property,
// and that has been changed in the JSON since it was downloaded.
- restartForKitAndReDownloadConfigFile();
+ reDownloadConfig = true;
break;
}
else if (!stampPtr.isEmpty())
{
- // Third case: Font has been downloaded already, has a "stamp" property, and
+ // Third case: asset has been downloaded already, has a "stamp" property, and
// that has *not* changed in the JSON since it was downloaded.
- fonts[uri].active = true;
+ assets[uri].active = true;
}
else
{
- // Last case: Font has been downloaded but does not have a "stamp" property.
+ // Last case: Asset has been downloaded but does not have a "stamp" property.
// Use ETag.
- if (!eTagUnchanged(uri, fonts[uri].eTag))
+ if (!eTagUnchanged(uri, assets[uri].eTag))
{
- restartForKitAndReDownloadConfigFile();
+ reDownloadConfig = true;
break;
}
- fonts[uri].active = true;
+ assets[uri].active = true;
}
}
}
}
- // Any font that has been deleted from the JSON needs to be removed on this side, too.
- for (const auto& it : fonts)
+ // Any asset that has been deleted from the JSON needs to be removed on this side, too.
+ for (const auto& it : assets)
{
if (!it.second.active)
{
- LOG_DBG("Font no longer mentioned in the remote font config: " << it.first);
- restartForKitAndReDownloadConfigFile();
+ LOG_DBG("Asset no longer mentioned in the remote font config: " << it.first);
+ reDownloadConfig = true;
break;
}
}
+ return reDownloadConfig;
}
-void RemoteFontConfigPoll::handleUnchangedJSON()
+std::string RemoteAssetConfigPoll::removeTemplate(const std::string& uri)
{
- // Iterate over the fonts that were mentioned in the JSON file when it was last downloaded.
- for (auto& it : fonts)
+ const Poco::URI assetUri{ uri };
+ const std::string& path = assetUri.getPath();
+ const std::string filename = path.substr(path.find_last_of('/') + 1);
+ std::string assetFile;
+ assetFile.append(COOLWSD::TmpTemplateDir);
+ assetFile.append("/");
+ assetFile.append(filename);
+ FileUtil::removeFile(assetFile);
+ return assetFile;
+}
+
+void RemoteAssetConfigPoll::reDownloadConfigFile(std::map& assets,
+ const std::string& assetType)
+{
+ LOG_DBG("Downloaded asset has been updated or a asset has been removed.");
+
+ // remove inactive templates
+ if (assetType == "templates")
{
- // If the JSON has a "stamp" for the font, and we have already downloaded it, by
+ for (const auto& it : assets)
+ if (!it.second.active)
+ removeTemplate(it.second.pathName);
+ }
+
+ assets.clear();
+ // Clear the saved ETag of the remote font configuration file so that it will be
+ // re-downloaded, and all fonts mentioned in it re-downloaded and fed to ForKit.
+ _eTagValue.clear();
+ if (assetType == "fonts")
+ {
+ LOG_DBG("ForKit must be restarted.");
+ COOLWSD::sendMessageToForKit("exit");
+ }
+}
+
+void RemoteAssetConfigPoll::handleJSON(const Poco::JSON::Object::Ptr& remoteJson)
+{
+ bool reDownloadFontConfig = getNewAssets(remoteJson, "fonts", fonts);
+ bool reDownloadTemplateConfig = getNewAssets(remoteJson, "templates", templates);
+
+ if (reDownloadFontConfig)
+ reDownloadConfigFile(fonts, "fonts");
+ if (reDownloadTemplateConfig)
+ reDownloadConfigFile(templates, "templates");
+}
+
+bool RemoteAssetConfigPoll::handleUnchangedAssets(std::map& assets)
+{
+ bool reDownloadConfig = false;
+
+ // Iterate over the assets that were mentioned in the JSON file when it was last downloaded.
+ for (auto& it : assets)
+ {
+ // If the JSON has a "stamp" for the asset, and we have already downloaded it, by
// definition we don't need to do anything when the JSON file has not changed.
if (it.second.stamp != "" && it.second.pathName != "")
continue;
@@ -663,37 +721,51 @@ void RemoteFontConfigPoll::handleUnchangedJSON()
// assert() that?
if (it.second.stamp != "" && it.second.pathName == "")
{
- LOG_WRN("Font at " << it.first << " was not downloaded, should have been");
+ LOG_WRN("Asset at " << it.first << " was not downloaded, should have been");
continue;
}
- // Otherwise use the ETag to check if the font file needs re-downloading.
+ // Otherwise use the ETag to check if the asset file needs re-downloading.
if (!eTagUnchanged(it.first, it.second.eTag))
{
- restartForKitAndReDownloadConfigFile();
+ reDownloadConfig = true;
break;
}
}
+ return reDownloadConfig;
+}
+
+void RemoteAssetConfigPoll::handleUnchangedJSON()
+{
+ bool reDownloadFontConfig = handleUnchangedAssets(fonts);
+ bool reDownloadTemplateConfig = handleUnchangedAssets(templates);
+
+ if (reDownloadFontConfig)
+ reDownloadConfigFile(fonts, "fonts");
+ if (reDownloadTemplateConfig)
+ reDownloadConfigFile(templates, "templates");
}
-bool RemoteFontConfigPoll::downloadPlain(const std::string& uri)
+bool RemoteAssetConfigPoll::downloadPlain(const std::string& uri,
+ std::map& assets,
+ const std::string& assetType)
{
- const Poco::URI fontUri{ uri };
- std::shared_ptr httpSession(StorageConnectionManager::getHttpSession(fontUri));
- http::Request request(fontUri.getPathAndQuery());
+ const Poco::URI assetUri{ uri };
+ std::shared_ptr httpSession(StorageConnectionManager::getHttpSession(assetUri));
+ http::Request request(assetUri.getPathAndQuery());
request.set("User-Agent", http::getAgentString());
const std::shared_ptr httpResponse = httpSession->syncRequest(request);
- return finishDownload(uri, httpResponse);
+ return finishDownload(uri, httpResponse, assets, assetType);
}
-bool RemoteFontConfigPoll::eTagUnchanged(const std::string& uri, const std::string& oldETag)
+bool RemoteAssetConfigPoll::eTagUnchanged(const std::string& uri, const std::string& oldETag)
{
- const Poco::URI fontUri{ uri };
- std::shared_ptr httpSession(StorageConnectionManager::getHttpSession(fontUri));
- http::Request request(fontUri.getPathAndQuery());
+ const Poco::URI assetUri{ uri };
+ std::shared_ptr httpSession(StorageConnectionManager::getHttpSession(assetUri));
+ http::Request request(assetUri.getPathAndQuery());
if (!oldETag.empty())
{
@@ -713,11 +785,13 @@ bool RemoteFontConfigPoll::eTagUnchanged(const std::string& uri, const std::stri
return false;
}
-bool RemoteFontConfigPoll::downloadWithETag(const std::string& uri, const std::string& oldETag)
+bool RemoteAssetConfigPoll::downloadWithETag(const std::string& uri, const std::string& oldETag,
+ std::map& assets,
+ const std::string& assetType)
{
- const Poco::URI fontUri{ uri };
- std::shared_ptr httpSession(StorageConnectionManager::getHttpSession(fontUri));
- http::Request request(fontUri.getPathAndQuery());
+ const Poco::URI assetUri{ uri };
+ std::shared_ptr httpSession(StorageConnectionManager::getHttpSession(assetUri));
+ http::Request request(assetUri.getPathAndQuery());
if (!oldETag.empty())
{
@@ -734,15 +808,16 @@ bool RemoteFontConfigPoll::downloadWithETag(const std::string& uri, const std::s
return true;
}
- if (!finishDownload(uri, httpResponse))
+ if (!finishDownload(uri, httpResponse, assets, assetType))
return false;
- fonts[uri].eTag = httpResponse->get("ETag");
+ assets[uri].eTag = httpResponse->get("ETag");
return true;
}
-bool RemoteFontConfigPoll::finishDownload(const std::string& uri,
- const std::shared_ptr& httpResponse)
+bool RemoteAssetConfigPoll::finishDownload(
+ const std::string& uri, const std::shared_ptr& httpResponse,
+ std::map& assets, const std::string& assetType)
{
if (httpResponse->statusLine().statusCode() != http::StatusCode::OK)
{
@@ -752,45 +827,38 @@ bool RemoteFontConfigPoll::finishDownload(const std::string& uri,
const std::string& body = httpResponse->getBody();
- // We intentionally use a new file name also when an updated version of a font is
- // downloaded. It causes trouble to rewrite the same file, in case it is in use in some Kit
- // process at the moment.
+ std::string assetFile;
+ if (assetType == "fonts")
+ // We intentionally use a new file name also when an updated version of a font is
+ // downloaded. It causes trouble to rewrite the same file, in case it is in use in some Kit
+ // process at the moment.
- // We don't remove the old file either as that also causes problems.
+ // We don't remove the old file either as that also causes problems.
+ // And in reality, it is a bit unclear how likely it even is that assets downloaded through
+ // this mechanism even will be updated.
+ assetFile = COOLWSD::TmpFontDir + '/' + Util::encodeId(Util::rng::getNext()) + ".ttf";
+ else if (assetType == "templates")
+ assetFile = removeTemplate(uri);
- // And in reality, it is a bit unclear how likely it even is that fonts downloaded through
- // this mechanism even will be updated.
- const std::string fontFile =
- COOLWSD::TmpFontDir + '/' + Util::encodeId(Util::rng::getNext()) + ".ttf";
-
- std::ofstream fontStream(fontFile);
- fontStream.write(body.data(), body.size());
- if (!fontStream.good())
+ std::ofstream assetStream(assetFile);
+ assetStream.write(body.data(), body.size());
+ if (!assetStream.good())
{
- LOG_ERR("Could not write " << body.size() << " bytes to [" << fontFile << ']');
+ LOG_ERR("Could not write " << body.size() << " bytes to [" << assetFile << ']');
return false;
}
- LOG_DBG("Got " << body.size() << " bytes from [" << uri << "] and wrote to [" << fontFile
+ LOG_DBG("Got " << body.size() << " bytes from [" << uri << "] and wrote to [" << assetFile
<< ']');
- fonts[uri].pathName = fontFile;
- COOLWSD::sendMessageToForKit("addfont " + fontFile);
+ assets[uri].pathName = assetFile;
+
+ if (assetType == "fonts")
+ COOLWSD::sendMessageToForKit("addfont " + assetFile);
COOLWSD::requestTerminateSpareKits();
return true;
}
-void RemoteFontConfigPoll::restartForKitAndReDownloadConfigFile()
-{
- LOG_DBG("Downloaded font has been updated or a font has been removed. ForKit must be "
- "restarted.");
- fonts.clear();
- // Clear the saved ETag of the remote font configuration file so that it will be
- // re-downloaded, and all fonts mentioned in it re-downloaded and fed to ForKit.
- _eTagValue.clear();
- COOLWSD::sendMessageToForKit("exit");
-}
-
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/wsd/RemoteConfig.hpp b/wsd/RemoteConfig.hpp
index c1a038851d98..de6f9d5d1578 100644
--- a/wsd/RemoteConfig.hpp
+++ b/wsd/RemoteConfig.hpp
@@ -22,6 +22,8 @@
#include
#include
+#include
+#include