xslt filter: set every libxslt transformation option explicitly

libxslt leaves each option unset on a fresh transformation context and takes
its own default for it.

The service registers no libxml input handlers, and a stylesheet only has to
produce the result tree, so set all of them and allow back local file reads
alone, which is how a stylesheet picks up a second file beside the one it
converts.

Signed-off-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Change-Id: I58abfc3919e26683d3fe2de72362f6c453eef8cf
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9939
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
(cherry picked from commit 02da3650b1bb71170112a480c7fa0220a2a34490)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210434
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Caolán McNamara
2026-09-14 12:56:48 +02:00
committed by Xisco Fauli
parent f8ed170f27
commit d5d950c2b9
3 changed files with 113 additions and 4 deletions
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:param name="targetBaseURL"/>
<xsl:template match="/">
<exsl:document href="{concat($targetBaseURL, 'xslt-write-test.txt')}" method="text">
<xsl:text>written</xsl:text>
</exsl:document>
<xsl:copy-of select="/"/>
</xsl:template>
</xsl:stylesheet>
+76 -1
View File
@@ -44,27 +44,38 @@ class XsltFilterTest
public:
void testXsltCopyOld();
void testXsltCopyNew();
void testXsltOutputStreamOnly();
CPPUNIT_TEST_SUITE(XsltFilterTest);
CPPUNIT_TEST(testXsltCopyOld);
CPPUNIT_TEST(testXsltCopyNew);
CPPUNIT_TEST(testXsltOutputStreamOnly);
CPPUNIT_TEST_SUITE_END();
};
class Listener : public ::cppu::WeakImplHelper<io::XStreamListener>
{
public:
Listener() : m_bDone(false) {}
/// Pass false for bFailOnError when the transformation is meant to fail.
Listener(bool bFailOnError = true)
: m_bDone(false), m_bFailOnError(bFailOnError), m_bError(false) {}
void wait() {
std::unique_lock<std::mutex> g(m_mutex);
m_cond.wait(g, [this]() { return m_bDone; });
}
bool hadError() {
std::scoped_lock<std::mutex> g(m_mutex);
return m_bError;
}
private:
std::mutex m_mutex;
std::condition_variable m_cond;
bool m_bDone;
bool m_bFailOnError;
bool m_bError;
virtual void SAL_CALL disposing(const lang::EventObject&) noexcept override {}
virtual void SAL_CALL started() noexcept override {}
@@ -72,8 +83,15 @@ private:
virtual void SAL_CALL terminated() noexcept override { notifyDone(); }
virtual void SAL_CALL error(const uno::Any& e) override
{
{
std::scoped_lock<std::mutex> g(m_mutex);
m_bError = true;
}
notifyDone(); // set on error too, otherwise main thread waits forever
SAL_WARN("filter.xslt", e);
// A failure thrown here would land on the transformation thread, so record it and let
// the test body check hadError.
if (m_bFailOnError)
CPPUNIT_FAIL("exception while in XSLT");
}
@@ -198,6 +216,63 @@ void XsltFilterTest::testXsltCopyOld()
osl_removeFile(tempURL.pData);
}
// The transformation writes to the output stream it is given, and nowhere else.
void XsltFilterTest::testXsltOutputStreamOnly()
{
OUString tempDirURL;
osl_getTempDirURL(&tempDirURL.pData);
oslFileHandle tempFile;
OUString tempURL;
osl::File::RC rc = osl::File::createTempFile(nullptr, &tempFile, &tempURL);
CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None, rc);
osl_closeFile(tempFile); // close it so xSFA can open it on WNT
OUString writtenURL(tempDirURL + "/xslt-write-test.txt");
osl_removeFile(writtenURL.pData);
OUString source(
m_directories.getURLFromSrc(u"/filter/source/xsltfilter/xsltfilter.component"));
uno::Sequence<uno::Any> args{
uno::Any(beans::NamedValue(u"StylesheetURL"_ustr,
uno::Any(m_directories.getURLFromSrc(u"/filter/qa/cppunit/data/xslt/document.xslt")))),
uno::Any(beans::NamedValue(u"SourceURL"_ustr, uno::Any(source))),
uno::Any(beans::NamedValue(u"TargetURL"_ustr, uno::Any(tempURL))),
uno::Any(beans::NamedValue(u"TargetBaseURL"_ustr, uno::Any(tempDirURL + "/"))),
uno::Any(beans::NamedValue(u"SystemType"_ustr, uno::Any(OUString()))),
uno::Any(beans::NamedValue(u"PublicType"_ustr, uno::Any(OUString())))
};
uno::Reference<ucb::XSimpleFileAccess3> xSFA =
ucb::SimpleFileAccess::create(getComponentContext());
uno::Reference<io::XInputStream> xIn = xSFA->openFileRead(source);
uno::Reference<io::XOutputStream> xOut = xSFA->openFileWrite(tempURL);
rtl::Reference<Listener> xListener = new Listener(false);
uno::Reference<xml::xslt::XXSLTTransformer> xXslt(
xml::xslt::XSLTTransformer::create(getComponentContext(), args));
xXslt->addListener(xListener);
xXslt->setInputStream(xIn);
xXslt->setOutputStream(xOut);
xXslt->start();
xListener->wait();
xIn->closeInput();
xOut->closeOutput();
xXslt->terminate();
CPPUNIT_ASSERT_MESSAGE("the transformation wrote outside its output stream",
!xSFA->exists(writtenURL));
CPPUNIT_ASSERT_MESSAGE("the transformation was not reported as failed",
xListener->hadError());
osl_removeFile(tempURL.pData);
}
CPPUNIT_TEST_SUITE_REGISTRATION(XsltFilterTest);
}
@@ -20,6 +20,7 @@
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <libxml/xmlstring.h>
#include <libxslt/security.h>
#include <libxslt/transform.h>
#include <libxslt/xsltutils.h>
#include <libxslt/variables.h>
@@ -307,6 +308,7 @@ namespace XSLT
xsltDebugDumpExtensions(NULL);
#endif
std::optional<OleHandler> oh(std::in_place, m_transformer->getComponentContext());
xsltSecurityPrefsPtr securityPrefs = nullptr;
if (styleSheet)
{
xsltTransformContextPtr tcontext = xsltNewTransformContext(
@@ -317,8 +319,23 @@ namespace XSLT
}
oh->registercontext(tcontext);
xsltQuoteUserParams(tcontext, params.data());
// A stylesheet builds the result tree and nothing else. Refuse every option libxslt
// has, then allow back the local reads, which is how a stylesheet picks up a second
// file that sits beside the one it converts.
securityPrefs = xsltNewSecurityPrefs();
if (securityPrefs)
{
for (auto eOption : { XSLT_SECPREF_READ_FILE, XSLT_SECPREF_WRITE_FILE,
XSLT_SECPREF_CREATE_DIRECTORY, XSLT_SECPREF_READ_NETWORK,
XSLT_SECPREF_WRITE_NETWORK })
xsltSetSecurityPrefs(securityPrefs, eOption, xsltSecurityForbid);
xsltSetSecurityPrefs(securityPrefs, XSLT_SECPREF_READ_FILE, xsltSecurityAllow);
}
if (securityPrefs && xsltSetCtxtSecurityPrefs(securityPrefs, tcontext) == 0)
result = xsltApplyStylesheetUser(styleSheet, doc, nullptr, nullptr, nullptr,
tcontext);
else
m_transformer->error(u"No security preferences were applied"_ustr);
}
if (result)
@@ -351,6 +368,7 @@ namespace XSLT
std::swap(m_tcontext, tcontext);
}
xsltFreeTransformContext(tcontext);
xsltFreeSecurityPrefs(securityPrefs);
xmlFreeDoc(doc);
xmlFreeDoc(result);
}