diff --git a/filter/qa/cppunit/data/xslt/document.xslt b/filter/qa/cppunit/data/xslt/document.xslt
new file mode 100644
index 000000000000..712df80e850d
--- /dev/null
+++ b/filter/qa/cppunit/data/xslt/document.xslt
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ written
+
+
+
+
+
diff --git a/filter/qa/cppunit/xslt-test.cxx b/filter/qa/cppunit/xslt-test.cxx
index 3ef87ad118ee..82c10aa4465f 100644
--- a/filter/qa/cppunit/xslt-test.cxx
+++ b/filter/qa/cppunit/xslt-test.cxx
@@ -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
{
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 g(m_mutex);
m_cond.wait(g, [this]() { return m_bDone; });
}
+ bool hadError() {
+ std::scoped_lock 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,9 +83,16 @@ private:
virtual void SAL_CALL terminated() noexcept override { notifyDone(); }
virtual void SAL_CALL error(const uno::Any& e) override
{
+ {
+ std::scoped_lock g(m_mutex);
+ m_bError = true;
+ }
notifyDone(); // set on error too, otherwise main thread waits forever
SAL_WARN("filter.xslt", e);
- CPPUNIT_FAIL("exception while in XSLT");
+ // 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");
}
void notifyDone() {
@@ -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 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 xSFA =
+ ucb::SimpleFileAccess::create(getComponentContext());
+
+ uno::Reference xIn = xSFA->openFileRead(source);
+ uno::Reference xOut = xSFA->openFileWrite(tempURL);
+
+ rtl::Reference xListener = new Listener(false);
+
+ uno::Reference 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);
}
diff --git a/filter/source/xsltfilter/LibXSLTTransformer.cxx b/filter/source/xsltfilter/LibXSLTTransformer.cxx
index 4defe3ff160d..2282ff0e9b3e 100644
--- a/filter/source/xsltfilter/LibXSLTTransformer.cxx
+++ b/filter/source/xsltfilter/LibXSLTTransformer.cxx
@@ -20,6 +20,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -307,6 +308,7 @@ namespace XSLT
xsltDebugDumpExtensions(NULL);
#endif
std::optional 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());
- result = xsltApplyStylesheetUser(styleSheet, doc, nullptr, nullptr, nullptr,
- tcontext);
+ // 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);
}