1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 
21 #include <cppuhelper/factory.hxx>
22 #include <cppuhelper/implbase.hxx>
23 
24 #include <sax/tools/documenthandleradapter.hxx>
25 
26 #include <osl/diagnose.h>
27 #include <osl/time.h>
28 #include <osl/conditn.hxx>
29 #include <rtl/strbuf.hxx>
30 #include <tools/urlobj.hxx>
31 #include <tools/diagnose_ex.h>
32 #include <sal/log.hxx>
33 
34 #include <comphelper/interaction.hxx>
35 #include <comphelper/processfactory.hxx>
36 
37 #include <com/sun/star/lang/XComponent.hpp>
38 #include <com/sun/star/lang/EventObject.hpp>
39 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
40 
41 #include <com/sun/star/uno/Any.hxx>
42 
43 #include <com/sun/star/beans/PropertyValue.hpp>
44 
45 #include <com/sun/star/xml/sax/Parser.hpp>
46 #include <com/sun/star/xml/sax/InputSource.hpp>
47 #include <com/sun/star/xml/sax/XDocumentHandler.hpp>
48 #include <com/sun/star/xml/sax/XExtendedDocumentHandler.hpp>
49 #include <com/sun/star/xml/sax/XFastParser.hpp>
50 #include <com/sun/star/xml/sax/SAXException.hpp>
51 #include <com/sun/star/xml/sax/Writer.hpp>
52 #include <com/sun/star/xml/XImportFilter.hpp>
53 #include <com/sun/star/xml/XExportFilter.hpp>
54 
55 #include <com/sun/star/util/theMacroExpander.hpp>
56 
57 #include <com/sun/star/io/Pipe.hpp>
58 #include <com/sun/star/io/XInputStream.hpp>
59 #include <com/sun/star/io/XOutputStream.hpp>
60 #include <com/sun/star/io/XActiveDataSource.hpp>
61 #include <com/sun/star/io/XActiveDataSink.hpp>
62 #include <com/sun/star/io/XActiveDataControl.hpp>
63 #include <com/sun/star/io/XSeekable.hpp>
64 #include <com/sun/star/io/XStreamListener.hpp>
65 #include <com/sun/star/util/PathSubstitution.hpp>
66 #include <com/sun/star/util/XStringSubstitution.hpp>
67 #include <com/sun/star/beans/NamedValue.hpp>
68 #include <com/sun/star/task/XInteractionHandler.hpp>
69 #include <com/sun/star/task/XInteractionRequest.hpp>
70 #include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
71 #include <com/sun/star/xml/xslt/XSLT2Transformer.hpp>
72 #include <com/sun/star/xml/xslt/XSLTTransformer.hpp>
73 
74 #include <xmloff/attrlist.hxx>
75 
76 #include "LibXSLTTransformer.hxx"
77 
78 #define TRANSFORMATION_TIMEOUT_SEC 60
79 
80 using namespace ::cppu;
81 using namespace ::osl;
82 using namespace ::sax;
83 using namespace ::com::sun::star::beans;
84 using namespace ::com::sun::star::io;
85 using namespace ::com::sun::star::uno;
86 using namespace ::com::sun::star::lang;
87 using namespace ::com::sun::star::registry;
88 using namespace ::com::sun::star::xml;
89 using namespace ::com::sun::star::xml::sax;
90 using namespace ::com::sun::star::util;
91 using namespace ::com::sun::star::task;
92 
93 namespace XSLT
94 {
95     /*
96      * XSLTFilter reads flat XML streams from the XML filter framework and passes
97      * them to an XSLT transformation service. XSLT transformation errors are
98      * reported to XSLTFilter.
99      *
100      * Currently, our transformation service is libxslt based, so it
101      * only supports XSLT 1.0. There is a possibility to use XSLT 2.0
102      * supporting service from an extension for a specific filter; the
103      * service must support com.sun.star.xml.xslt.XSLT2Transformer.
104      */
105     class XSLTFilter : public WeakImplHelper<XImportFilter, XExportFilter,
106             XStreamListener, ExtendedDocumentHandlerAdapter>
107     {
108     private:
109 
110         // the UNO ServiceFactory
111         css::uno::Reference<XComponentContext> m_xContext;
112 
113         // DocumentHandler interface of the css::xml::sax::Writer service
114         css::uno::Reference<XOutputStream> m_rOutputStream;
115 
116         css::uno::Reference<xslt::XXSLTTransformer> m_tcontrol;
117 
118         osl::Condition m_cTransformed;
119         bool m_bTerminated;
120         bool m_bError;
121 
122         OUString m_aExportBaseUrl;
123 
124         OUString
125         rel2abs(const OUString&);
126         OUString
127         expandUrl(const OUString&);
128 
129         css::uno::Reference<xslt::XXSLTTransformer> impl_createTransformer(const OUString& rTransformer, const Sequence<Any>& rArgs);
130 
131     public:
132 
133         // ctor...
134         explicit XSLTFilter(const css::uno::Reference<XComponentContext> &r);
135 
136         // XStreamListener
137         virtual void SAL_CALL
138         error(const Any& a) override;
139         virtual void SAL_CALL
140         closed() override;
141         virtual void SAL_CALL
142         terminated() override;
143         virtual void SAL_CALL
144         started() override;
145         virtual void SAL_CALL
146         disposing(const EventObject& e) override;
147 
148         // XImportFilter
149         virtual sal_Bool SAL_CALL
150         importer(const Sequence<PropertyValue>& aSourceData, const css::uno::Reference<
151                 XDocumentHandler>& xHandler,
152                 const Sequence<OUString>& msUserData) override;
153 
154         // XExportFilter
155         virtual sal_Bool SAL_CALL
156         exporter(const Sequence<PropertyValue>& aSourceData, const Sequence<
157                 OUString>& msUserData) override;
158 
159         // XDocumentHandler
160         virtual void SAL_CALL
161         startDocument() override;
162         virtual void SAL_CALL
163         endDocument() override;
164     };
165 
XSLTFilter(const css::uno::Reference<XComponentContext> & r)166     XSLTFilter::XSLTFilter(const css::uno::Reference<XComponentContext> &r):
167         m_xContext(r), m_bTerminated(false), m_bError(false)
168     {}
169 
170     void
disposing(const EventObject &)171     XSLTFilter::disposing(const EventObject&)
172     {
173     }
174 
175     OUString
expandUrl(const OUString & sUrl)176     XSLTFilter::expandUrl(const OUString& sUrl)
177     {
178         OUString sExpandedUrl;
179         try
180             {
181                 css::uno::Reference<XMacroExpander>
182                         xMacroExpander = theMacroExpander::get(m_xContext);
183                 sExpandedUrl = xMacroExpander->expandMacros(sUrl);
184                 sal_Int32 nPos = sExpandedUrl.indexOf( "vnd.sun.star.expand:" );
185                 if (nPos != -1)
186                     sExpandedUrl = sExpandedUrl.copy(nPos + 20);
187             }
188         catch (const Exception&)
189             {
190             }
191         return sExpandedUrl;
192     }
193 
194     css::uno::Reference<xslt::XXSLTTransformer>
impl_createTransformer(const OUString & rTransformer,const Sequence<Any> & rArgs)195     XSLTFilter::impl_createTransformer(const OUString& rTransformer, const Sequence<Any>& rArgs)
196     {
197         css::uno::Reference<xslt::XXSLTTransformer> xTransformer;
198 
199         // check if the filter needs XSLT-2.0-capable transformer
200         // COMPATIBILITY: libreoffice 3.5/3.6 used to save the impl.
201         // name of the XSLT 2.0 transformation service there, so check
202         // for that too (it is sufficient to check that there is _a_
203         // service name there)
204         if (rTransformer.toBoolean() || rTransformer.startsWith("com.sun."))
205         {
206             try
207             {
208                 xTransformer = xslt::XSLT2Transformer::create(m_xContext, rArgs);
209             }
210             catch (const Exception&)
211             {
212                 // TODO: put a dialog telling about the need to install
213                 // xslt2-transformer extension here
214                 SAL_WARN("filter.xslt", "could not create XSLT 2.0 transformer");
215                 throw;
216             }
217         }
218 
219         // instantiation of XSLT 2.0 transformer service failed, or the
220         // filter does not need it
221         if (!xTransformer.is())
222         {
223             xTransformer = xslt::XSLTTransformer::create(m_xContext, rArgs);
224         }
225 
226         return xTransformer;
227     }
228 
229     void
started()230     XSLTFilter::started()
231     {
232         m_cTransformed.reset();
233     }
234     void
error(const Any & a)235     XSLTFilter::error(const Any& a)
236     {
237         SAL_WARN("filter.xslt", "XSLTFilter::error was called: " << exceptionToString(a));
238         m_bError = true;
239         m_cTransformed.set();
240     }
241     void
closed()242     XSLTFilter::closed()
243     {
244         m_cTransformed.set();
245     }
246     void
terminated()247     XSLTFilter::terminated()
248     {
249         m_bTerminated = true;
250         m_cTransformed.set();
251     }
252 
253     OUString
rel2abs(const OUString & s)254     XSLTFilter::rel2abs(const OUString& s)
255     {
256 
257         css::uno::Reference<XStringSubstitution>
258                 subs(css::util::PathSubstitution::create(m_xContext));
259         OUString aWorkingDir(subs->getSubstituteVariableValue( "$(progurl)" ));
260         INetURLObject aObj(aWorkingDir);
261         aObj.setFinalSlash();
262         bool bWasAbsolute;
263         INetURLObject aURL = aObj.smartRel2Abs(s, bWasAbsolute, false,
264                 INetURLObject::EncodeMechanism::WasEncoded, RTL_TEXTENCODING_UTF8, true);
265         return aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
266     }
267 
268     sal_Bool
importer(const Sequence<PropertyValue> & aSourceData,const css::uno::Reference<XDocumentHandler> & xHandler,const Sequence<OUString> & msUserData)269     XSLTFilter::importer(const Sequence<PropertyValue>& aSourceData,
270             const css::uno::Reference<XDocumentHandler>& xHandler, const Sequence<
271                     OUString>& msUserData)
272     {
273         if (msUserData.getLength() < 5)
274             return false;
275 
276         OUString udStyleSheet = rel2abs(msUserData[4]);
277 
278         // get information from media descriptor
279         // the input stream that represents the imported file
280         // is most important here since we need to supply it to
281         // the sax parser that drives the supplied document handler
282         sal_Int32 nLength = aSourceData.getLength();
283         OUString aName, aURL;
284         css::uno::Reference<XInputStream> xInputStream;
285         css::uno::Reference<XInteractionHandler> xInterActionHandler;
286         for (sal_Int32 i = 0; i < nLength; i++)
287             {
288                 aName = aSourceData[i].Name;
289                 Any value = aSourceData[i].Value;
290                 if ( aName == "InputStream" )
291                     value >>= xInputStream;
292                 else if ( aName == "URL" )
293                     value >>= aURL;
294                 else if ( aName == "InteractionHandler" )
295                     value >>= xInterActionHandler;
296             }
297         OSL_ASSERT(xInputStream.is());
298         if (!xInputStream.is())
299             return false;
300 
301         // create SAX parser that will read the document file
302         // and provide events to xHandler passed to this call
303         css::uno::Reference<XParser> xSaxParser = Parser::create(m_xContext);
304 
305         // create transformer
306         Sequence<Any> args(3);
307         NamedValue nv;
308 
309         nv.Name = "StylesheetURL";
310         nv.Value <<= expandUrl(udStyleSheet);
311         args[0] <<= nv;
312         nv.Name = "SourceURL";
313         nv.Value <<= aURL;
314         args[1] <<= nv;
315         nv.Name = "SourceBaseURL";
316         nv.Value <<= INetURLObject(aURL).getBase();
317         args[2] <<= nv;
318 
319         m_tcontrol = impl_createTransformer(msUserData[1], args);
320 
321         OSL_ASSERT(xHandler.is());
322         OSL_ASSERT(xInputStream.is());
323         OSL_ASSERT(m_tcontrol.is());
324         if (xHandler.is() && xInputStream.is() && m_tcontrol.is())
325             {
326                 try
327                     {
328                         css::uno::Reference<css::io::XSeekable> xSeek(xInputStream, UNO_QUERY);
329                         if (xSeek.is())
330                             xSeek->seek(0);
331 
332                         // we want to be notified when the processing is done...
333                         m_tcontrol->addListener(css::uno::Reference<XStreamListener> (
334                                 this));
335 
336                         // connect input to transformer
337                         m_tcontrol->setInputStream(xInputStream);
338 
339                         // create pipe
340                         css::uno::Reference<XOutputStream> pipeout =
341                                         Pipe::create(m_xContext);
342                         css::uno::Reference<XInputStream> pipein(pipeout, UNO_QUERY);
343 
344                         //connect transformer to pipe
345                         m_tcontrol->setOutputStream(pipeout);
346 
347                         // connect pipe to sax parser
348                         InputSource aInput;
349                         aInput.sSystemId = aURL;
350                         aInput.sPublicId = aURL;
351                         aInput.aInputStream = pipein;
352 
353                         // set doc handler
354                         xSaxParser->setDocumentHandler(xHandler);
355                         css::uno::Reference< css::xml::sax::XFastParser > xFastParser = dynamic_cast<
356                             css::xml::sax::XFastParser* >( xHandler.get() );
357 
358                         // transform
359                         m_tcontrol->start();
360                         TimeValue timeout = { TRANSFORMATION_TIMEOUT_SEC, 0};
361                         osl::Condition::Result result(m_cTransformed.wait(&timeout));
362                         while (osl::Condition::result_timeout == result) {
363                                 if (xInterActionHandler.is()) {
364                                         Sequence<Any> excArgs(0);
365                                         css::ucb::InteractiveAugmentedIOException exc(
366                                                 "Timeout!",
367                                                 static_cast< OWeakObject * >( this ),
368                                                 InteractionClassification_ERROR,
369                                                 css::ucb::IOErrorCode_GENERAL,
370                                                  excArgs);
371                                         Any r;
372                                         r <<= exc;
373                                         ::comphelper::OInteractionRequest* pRequest = new ::comphelper::OInteractionRequest(r);
374                                         css::uno::Reference< XInteractionRequest > xRequest(pRequest);
375                                         ::comphelper::OInteractionRetry* pRetry = new ::comphelper::OInteractionRetry;
376                                         ::comphelper::OInteractionAbort* pAbort = new ::comphelper::OInteractionAbort;
377                                         pRequest->addContinuation(pRetry);
378                                         pRequest->addContinuation(pAbort);
379                                         xInterActionHandler->handle(xRequest);
380                                         if (pAbort->wasSelected()) {
381                                                 m_bError = true;
382                                                 m_cTransformed.set();
383                                         }
384                                 }
385                                 result = m_cTransformed.wait(&timeout);
386                         };
387                         if (!m_bError) {
388                                 if( xFastParser.is() )
389                                     xFastParser->parseStream( aInput );
390                                 else
391                                     xSaxParser->parseStream( aInput );
392                         }
393                         m_tcontrol->terminate();
394                         return !m_bError;
395                     }
396                 catch( const Exception& )
397                     {
398                         // something went wrong
399                         TOOLS_WARN_EXCEPTION("filter.xslt", "");
400                         return false;
401                     }
402             }
403         else
404             {
405                 return false;
406             }
407     }
408 
409     sal_Bool
exporter(const Sequence<PropertyValue> & aSourceData,const Sequence<OUString> & msUserData)410     XSLTFilter::exporter(const Sequence<PropertyValue>& aSourceData,
411             const Sequence<OUString>& msUserData)
412     {
413         if (msUserData.getLength() < 6)
414             return false;
415 
416         // get interesting values from user data
417         OUString udStyleSheet = rel2abs(msUserData[5]);
418 
419         // read source data
420         // we are especially interested in the output stream
421         // since that is where our xml-writer will push the data
422         // from its data-source interface
423         OUString aName, sURL;
424         OUString aDoctypePublic;
425         // css::uno::Reference<XOutputStream> rOutputStream;
426         sal_Int32 nLength = aSourceData.getLength();
427         for (sal_Int32 i = 0; i < nLength; i++)
428             {
429                 aName = aSourceData[i].Name;
430                 if ( aName == "DocType_Public" )
431                     aSourceData[i].Value >>= aDoctypePublic;
432                 else if ( aName == "OutputStream" )
433                     aSourceData[i].Value >>= m_rOutputStream;
434                 else if ( aName == "URL" )
435                     aSourceData[i].Value >>= sURL;
436             }
437 
438         if (!getDelegate().is())
439             {
440                 // get the document writer
441                 setDelegate(css::uno::Reference<XExtendedDocumentHandler>(
442                                 Writer::create(m_xContext),
443                                 UNO_QUERY_THROW));
444             }
445 
446         // create transformer
447         Sequence<Any> args(4);
448         NamedValue nv;
449         nv.Name = "StylesheetURL";
450         nv.Value <<= expandUrl(udStyleSheet);
451         args[0] <<= nv;
452         nv.Name = "TargetURL";
453         nv.Value <<= sURL;
454         args[1] <<= nv;
455         nv.Name = "DoctypePublic";
456         nv.Value <<= aDoctypePublic;
457         args[2] <<= nv;
458         nv.Name = "TargetBaseURL";
459         INetURLObject ineturl(sURL);
460         ineturl.removeSegment();
461         m_aExportBaseUrl = ineturl.GetMainURL(INetURLObject::DecodeMechanism::NONE);
462         nv.Value <<= m_aExportBaseUrl;
463         args[3] <<= nv;
464 
465         m_tcontrol = impl_createTransformer(msUserData[1], args);
466 
467         OSL_ASSERT(m_rOutputStream.is());
468         OSL_ASSERT(m_tcontrol.is());
469         if (m_tcontrol.is() && m_rOutputStream.is())
470             {
471                 // we want to be notified when the processing is done...
472                 m_tcontrol->addListener(css::uno::Reference<XStreamListener> (this));
473 
474                 // create pipe
475                 css::uno::Reference<XOutputStream> pipeout =
476                                 Pipe::create(m_xContext);
477                 css::uno::Reference<XInputStream> pipein(pipeout, UNO_QUERY);
478 
479                 // connect sax writer to pipe
480                 css::uno::Reference<XActiveDataSource> xmlsource(getDelegate(),
481                         UNO_QUERY);
482                 xmlsource->setOutputStream(pipeout);
483 
484                 // connect pipe to transformer
485                 m_tcontrol->setInputStream(pipein);
486 
487                 // connect transformer to output
488                 m_tcontrol->setOutputStream(m_rOutputStream);
489 
490                 // we will start receiving events after returning 'true'.
491                 // we will start the transformation as soon as we receive the startDocument
492                 // event.
493                 return true;
494             }
495         else
496             {
497                 return false;
498             }
499     }
500 
501     // for the DocumentHandler implementation, we just proxy the
502     // events to the XML writer that we created upon the output stream
503     // that was provided by the XMLFilterAdapter
504     void
startDocument()505     XSLTFilter::startDocument()
506     {
507         ExtendedDocumentHandlerAdapter::startDocument();
508         m_tcontrol->start();
509     }
510 
511     void
endDocument()512     XSLTFilter::endDocument()
513     {
514         ExtendedDocumentHandlerAdapter::endDocument();
515         // wait for the transformer to finish
516         m_cTransformed.wait();
517         m_tcontrol->terminate();
518         if (m_bError || m_bTerminated)
519             throw RuntimeException();
520     }
521 
522 
523     // Component management
524 
525 #define FILTER_SERVICE_NAME "com.sun.star.documentconversion.XSLTFilter"
526 #define FILTER_IMPL_NAME "com.sun.star.comp.documentconversion.XSLTFilter"
527 #define TRANSFORMER_SERVICE_NAME "com.sun.star.xml.xslt.XSLTTransformer"
528 #define TRANSFORMER_IMPL_NAME "com.sun.star.comp.documentconversion.LibXSLTTransformer"
529 
530     static css::uno::Reference<XInterface>
CreateTransformerInstance(const css::uno::Reference<XMultiServiceFactory> & r)531     CreateTransformerInstance(const css::uno::Reference<XMultiServiceFactory> &r)
532     {
533         return css::uno::Reference<XInterface> (static_cast<OWeakObject *>(new LibXSLTTransformer( comphelper::getComponentContext(r) )));
534     }
535 
536     static css::uno::Reference<XInterface>
CreateFilterInstance(const css::uno::Reference<XMultiServiceFactory> & r)537     CreateFilterInstance(const css::uno::Reference<XMultiServiceFactory> &r)
538     {
539         return css::uno::Reference<XInterface> (static_cast<OWeakObject *>(new XSLTFilter( comphelper::getComponentContext(r) )));
540     }
541 
542 }
543 
544 using namespace XSLT;
545 
546 extern "C"
547 {
xsltfilter_component_getFactory(const sal_Char * pImplName,void * pServiceManager,void *)548     SAL_DLLPUBLIC_EXPORT void * xsltfilter_component_getFactory(const sal_Char * pImplName,
549             void * pServiceManager, void * /* pRegistryKey */)
550     {
551         void * pRet = nullptr;
552 
553         if (pServiceManager)
554             {
555                 if (rtl_str_compare(pImplName, FILTER_IMPL_NAME) == 0)
556                     {
557                         Sequence<OUString> serviceNames { FILTER_SERVICE_NAME };
558 
559                         css::uno::Reference<XSingleServiceFactory>
560                                 xFactory(
561                                         createSingleFactory(
562                                                 static_cast<XMultiServiceFactory *> (pServiceManager),
563                                                 OUString::createFromAscii(
564                                                         pImplName),
565                                                 CreateFilterInstance,
566                                                 serviceNames));
567 
568                         if (xFactory.is())
569                             {
570                                 xFactory->acquire();
571                                 pRet = xFactory.get();
572                             }
573                     }
574                 else if (rtl_str_compare(pImplName, TRANSFORMER_IMPL_NAME) == 0)
575                     {
576                         Sequence<OUString> serviceNames { TRANSFORMER_SERVICE_NAME };
577                         css::uno::Reference<XSingleServiceFactory>
578                                 xFactory(
579                                         createSingleFactory(
580                                                 static_cast<XMultiServiceFactory *> (pServiceManager),
581                                                 OUString::createFromAscii(
582                                                         pImplName),
583                                                 CreateTransformerInstance,
584                                                 serviceNames));
585 
586                         if (xFactory.is())
587                             {
588                                 xFactory->acquire();
589                                 pRet = xFactory.get();
590                             }
591 
592                     }
593             }
594         return pRet;
595     }
596 
597 } // extern "C"
598 
599 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
600