xref: /core/oox/source/core/filterbase.cxx (revision 36543fc4)
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 #include <sal/config.h>
21 
22 #include <com/sun/star/container/XNameAccess.hpp>
23 #include <com/sun/star/drawing/XShape.hpp>
24 #include <com/sun/star/frame/XModel.hpp>
25 #include <com/sun/star/io/XStream.hpp>
26 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
27 #include <com/sun/star/task/XInteractionHandler.hpp>
28 #include <com/sun/star/task/XStatusIndicator.hpp>
29 #include <com/sun/star/uno/XComponentContext.hpp>
30 #include <cppuhelper/supportsservice.hxx>
31 #include <comphelper/documentconstants.hxx>
32 #include <comphelper/sequence.hxx>
33 #include <unotools/mediadescriptor.hxx>
34 #include <osl/mutex.hxx>
35 #include <osl/diagnose.h>
36 #include <rtl/instance.hxx>
37 #include <rtl/uri.hxx>
38 #include <memory>
39 #include <set>
40 
41 #include <oox/core/filterbase.hxx>
42 #include <oox/helper/binaryinputstream.hxx>
43 #include <oox/helper/binaryoutputstream.hxx>
44 #include <oox/helper/graphichelper.hxx>
45 #include <oox/helper/modelobjecthelper.hxx>
46 #include <oox/ole/oleobjecthelper.hxx>
47 #include <oox/ole/vbaproject.hxx>
48 
49 namespace oox {
50 namespace core {
51 
52 using namespace ::com::sun::star::beans;
53 using namespace ::com::sun::star::frame;
54 using namespace ::com::sun::star::graphic;
55 using namespace ::com::sun::star::drawing;
56 using namespace ::com::sun::star::io;
57 using namespace ::com::sun::star::lang;
58 using namespace ::com::sun::star::task;
59 using namespace ::com::sun::star::uno;
60 
61 using ::com::sun::star::container::XNameAccess;
62 using utl::MediaDescriptor;
63 using ::comphelper::SequenceAsHashMap;
64 using ::oox::ole::OleObjectHelper;
65 using ::oox::ole::VbaProject;
66 
67 namespace {
68 
69 struct UrlPool
70 {
71     ::osl::Mutex        maMutex;
72     ::std::set< OUString > maUrls;
73 };
74 
75 struct StaticUrlPool : public ::rtl::Static< UrlPool, StaticUrlPool > {};
76 
77 /** This guard prevents recursive loading/saving of the same document. */
78 class DocumentOpenedGuard
79 {
80 public:
81     explicit            DocumentOpenedGuard( const OUString& rUrl );
82                         ~DocumentOpenedGuard();
83                         DocumentOpenedGuard(const DocumentOpenedGuard&) = delete;
84     DocumentOpenedGuard& operator=(const DocumentOpenedGuard&) = delete;
85 
86     bool         isValid() const { return mbValid; }
87 
88 private:
89     OUString            maUrl;
90     bool                mbValid;
91 };
92 
93 DocumentOpenedGuard::DocumentOpenedGuard( const OUString& rUrl )
94 {
95     UrlPool& rUrlPool = StaticUrlPool::get();
96     ::osl::MutexGuard aGuard( rUrlPool.maMutex );
97     mbValid = rUrl.isEmpty() || (rUrlPool.maUrls.count( rUrl ) == 0);
98     if( mbValid && !rUrl.isEmpty() )
99     {
100         rUrlPool.maUrls.insert( rUrl );
101         maUrl = rUrl;
102     }
103 }
104 
105 DocumentOpenedGuard::~DocumentOpenedGuard()
106 {
107     UrlPool& rUrlPool = StaticUrlPool::get();
108     ::osl::MutexGuard aGuard( rUrlPool.maMutex );
109     if( !maUrl.isEmpty() )
110         rUrlPool.maUrls.erase( maUrl );
111 }
112 
113 } // namespace
114 
115 /** Specifies whether this filter is an import or export filter. */
116 enum FilterDirection
117 {
118     FILTERDIRECTION_UNKNOWN,
119     FILTERDIRECTION_IMPORT,
120     FILTERDIRECTION_EXPORT
121 };
122 
123 struct FilterBaseImpl
124 {
125     typedef std::shared_ptr< GraphicHelper >        GraphicHelperRef;
126     typedef std::shared_ptr< ModelObjectHelper >    ModelObjHelperRef;
127     typedef std::shared_ptr< OleObjectHelper >      OleObjHelperRef;
128     typedef std::shared_ptr< VbaProject >           VbaProjectRef;
129 
130     FilterDirection     meDirection;
131     SequenceAsHashMap   maArguments;
132     SequenceAsHashMap   maFilterData;
133     MediaDescriptor     maMediaDesc;
134     OUString            maFileUrl;
135     StorageRef          mxStorage;
136     OoxmlVersion        meVersion;
137 
138     GraphicHelperRef    mxGraphicHelper;        /// Graphic and graphic object handling.
139     ModelObjHelperRef   mxModelObjHelper;       /// Tables to create new named drawing objects.
140     OleObjHelperRef     mxOleObjHelper;         /// OLE object handling.
141     VbaProjectRef       mxVbaProject;           /// VBA project manager.
142 
143     Reference< XComponentContext >      mxComponentContext;
144     Reference< XModel >                 mxModel;
145     Reference< XMultiServiceFactory >   mxModelFactory;
146     Reference< XFrame >                 mxTargetFrame;
147     Reference< XInputStream >           mxInStream;
148     Reference< XStream >                mxOutStream;
149     Reference< XStatusIndicator >       mxStatusIndicator;
150     Reference< XInteractionHandler >    mxInteractionHandler;
151     Reference< XShape >                 mxParentShape;
152 
153     bool mbExportVBA;
154 
155     bool mbExportTemplate;
156 
157     /// @throws RuntimeException
158     explicit            FilterBaseImpl( const Reference< XComponentContext >& rxContext );
159 
160     /// @throws IllegalArgumentException
161     void                setDocumentModel( const Reference< XComponent >& rxComponent );
162 
163     void                initializeFilter();
164 };
165 
166 FilterBaseImpl::FilterBaseImpl( const Reference< XComponentContext >& rxContext ) :
167     meDirection( FILTERDIRECTION_UNKNOWN ),
168     meVersion( ECMA_DIALECT ),
169     mxComponentContext( rxContext, UNO_SET_THROW ),
170     mbExportVBA(false),
171     mbExportTemplate(false)
172 {
173 }
174 
175 void FilterBaseImpl::setDocumentModel( const Reference< XComponent >& rxComponent )
176 {
177     try
178     {
179         mxModel.set( rxComponent, UNO_QUERY_THROW );
180         mxModelFactory.set( rxComponent, UNO_QUERY_THROW );
181     }
182     catch( Exception& )
183     {
184         throw IllegalArgumentException();
185     }
186 }
187 
188 void FilterBaseImpl::initializeFilter()
189 {
190     try
191     {
192         // lock the model controllers
193         mxModel->lockControllers();
194     }
195     catch( Exception& )
196     {
197     }
198 }
199 
200 FilterBase::FilterBase( const Reference< XComponentContext >& rxContext ) :
201     mxImpl( new FilterBaseImpl( rxContext ) )
202 {
203 }
204 
205 FilterBase::~FilterBase()
206 {
207 }
208 
209 bool FilterBase::isImportFilter() const
210 {
211     return mxImpl->meDirection == FILTERDIRECTION_IMPORT;
212 }
213 
214 bool FilterBase::isExportFilter() const
215 {
216     return mxImpl->meDirection == FILTERDIRECTION_EXPORT;
217 }
218 
219 OoxmlVersion FilterBase::getVersion() const
220 {
221     return mxImpl->meVersion;
222 }
223 
224 const Reference< XComponentContext >& FilterBase::getComponentContext() const
225 {
226     return mxImpl->mxComponentContext;
227 }
228 
229 const Reference< XModel >& FilterBase::getModel() const
230 {
231     return mxImpl->mxModel;
232 }
233 
234 const Reference< XMultiServiceFactory >& FilterBase::getModelFactory() const
235 {
236     return mxImpl->mxModelFactory;
237 }
238 
239 const Reference< XFrame >& FilterBase::getTargetFrame() const
240 {
241     return mxImpl->mxTargetFrame;
242 }
243 
244 const Reference< XStatusIndicator >& FilterBase::getStatusIndicator() const
245 {
246     return mxImpl->mxStatusIndicator;
247 }
248 
249 MediaDescriptor& FilterBase::getMediaDescriptor() const
250 {
251     return mxImpl->maMediaDesc;
252 }
253 
254 SequenceAsHashMap& FilterBase::getFilterData() const
255 {
256     return mxImpl->maFilterData;
257 }
258 
259 const OUString& FilterBase::getFileUrl() const
260 {
261     return mxImpl->maFileUrl;
262 }
263 
264 namespace {
265 
266 bool lclIsDosDrive( const OUString& rUrl, sal_Int32 nPos = 0 )
267 {
268     return
269         (rUrl.getLength() >= nPos + 3) &&
270         ((('A' <= rUrl[ nPos ]) && (rUrl[ nPos ] <= 'Z')) || (('a' <= rUrl[ nPos ]) && (rUrl[ nPos ] <= 'z'))) &&
271         (rUrl[ nPos + 1 ] == ':') &&
272         (rUrl[ nPos + 2 ] == '/');
273 }
274 
275 } // namespace
276 
277 OUString FilterBase::getAbsoluteUrl( const OUString& rUrl ) const
278 {
279     // handle some special cases before calling ::rtl::Uri::convertRelToAbs()
280 
281     const OUString aFileSchema = "file:";
282     const OUString aFilePrefix = "file:///";
283     const sal_Int32 nFilePrefixLen = aFilePrefix.getLength();
284     const OUString aUncPrefix = "//";
285 
286     /*  (1) convert all backslashes to slashes, and check that passed URL is
287         not empty. */
288     OUString aUrl = rUrl.replace( '\\', '/' );
289     if( aUrl.isEmpty() )
290         return aUrl;
291 
292     /*  (2) add 'file:///' to absolute Windows paths, e.g. convert
293         'C:/path/file' to 'file:///c:/path/file'. */
294     if( lclIsDosDrive( aUrl ) )
295         return aFilePrefix + aUrl;
296 
297     /*  (3) add 'file:' to UNC paths, e.g. convert '//server/path/file' to
298         'file://server/path/file'. */
299     if( aUrl.match( aUncPrefix ) )
300         return aFileSchema + aUrl;
301 
302     /*  (4) remove additional slashes from UNC paths, e.g. convert
303         'file://///server/path/file' to 'file://server/path/file'. */
304     if( (aUrl.getLength() >= nFilePrefixLen + 2) &&
305         aUrl.match( aFilePrefix ) &&
306         aUrl.match( aUncPrefix, nFilePrefixLen ) )
307     {
308         return aFileSchema + aUrl.copy( nFilePrefixLen );
309     }
310 
311     /*  (5) handle URLs relative to current drive, e.g. the URL '/path1/file1'
312         relative to the base URL 'file:///C:/path2/file2' does not result in
313         the expected 'file:///C:/path1/file1', but in 'file:///path1/file1'. */
314     if( aUrl.startsWith("/") &&
315         mxImpl->maFileUrl.match( aFilePrefix ) &&
316         lclIsDosDrive( mxImpl->maFileUrl, nFilePrefixLen ) )
317     {
318         return mxImpl->maFileUrl.copy( 0, nFilePrefixLen + 3 ) + aUrl.copy( 1 );
319     }
320 
321     try
322     {
323         return ::rtl::Uri::convertRelToAbs( mxImpl->maFileUrl, aUrl );
324     }
325     catch( ::rtl::MalformedUriException& )
326     {
327     }
328     return aUrl;
329 }
330 
331 StorageRef const & FilterBase::getStorage() const
332 {
333     return mxImpl->mxStorage;
334 }
335 
336 Reference< XInputStream > FilterBase::openInputStream( const OUString& rStreamName ) const
337 {
338     if (!mxImpl->mxStorage)
339         throw RuntimeException();
340     return mxImpl->mxStorage->openInputStream( rStreamName );
341 }
342 
343 Reference< XOutputStream > FilterBase::openOutputStream( const OUString& rStreamName ) const
344 {
345     return mxImpl->mxStorage->openOutputStream( rStreamName );
346 }
347 
348 void FilterBase::commitStorage() const
349 {
350     mxImpl->mxStorage->commit();
351 }
352 
353 // helpers
354 
355 GraphicHelper& FilterBase::getGraphicHelper() const
356 {
357     if( !mxImpl->mxGraphicHelper )
358         mxImpl->mxGraphicHelper.reset( implCreateGraphicHelper() );
359     return *mxImpl->mxGraphicHelper;
360 }
361 
362 ModelObjectHelper& FilterBase::getModelObjectHelper() const
363 {
364     if( !mxImpl->mxModelObjHelper )
365         mxImpl->mxModelObjHelper.reset( new ModelObjectHelper( mxImpl->mxModelFactory ) );
366     return *mxImpl->mxModelObjHelper;
367 }
368 
369 OleObjectHelper& FilterBase::getOleObjectHelper() const
370 {
371     if( !mxImpl->mxOleObjHelper )
372         mxImpl->mxOleObjHelper.reset(new OleObjectHelper(mxImpl->mxModelFactory, mxImpl->mxModel));
373     return *mxImpl->mxOleObjHelper;
374 }
375 
376 VbaProject& FilterBase::getVbaProject() const
377 {
378     if( !mxImpl->mxVbaProject )
379         mxImpl->mxVbaProject.reset( implCreateVbaProject() );
380     return *mxImpl->mxVbaProject;
381 }
382 
383 bool FilterBase::importBinaryData( StreamDataSequence & orDataSeq, const OUString& rStreamName )
384 {
385     OSL_ENSURE( !rStreamName.isEmpty(), "FilterBase::importBinaryData - empty stream name" );
386     if( rStreamName.isEmpty() )
387         return false;
388 
389     // try to open the stream (this may fail - do not assert)
390     BinaryXInputStream aInStrm( openInputStream( rStreamName ), true );
391     if( aInStrm.isEof() )
392         return false;
393 
394     // copy the entire stream to the passed sequence
395     SequenceOutputStream aOutStrm( orDataSeq );
396     aInStrm.copyToStream( aOutStrm );
397     return true;
398 }
399 
400 // com.sun.star.lang.XServiceInfo interface
401 
402 sal_Bool SAL_CALL FilterBase::supportsService( const OUString& rServiceName )
403 {
404     return cppu::supportsService(this, rServiceName);
405 }
406 
407 Sequence< OUString > SAL_CALL FilterBase::getSupportedServiceNames()
408 {
409     return { "com.sun.star.document.ImportFilter", "com.sun.star.document.ExportFilter" };
410 }
411 
412 // com.sun.star.lang.XInitialization interface
413 
414 void SAL_CALL FilterBase::initialize( const Sequence< Any >& rArgs )
415 {
416     if( rArgs.getLength() >= 2 ) try
417     {
418         mxImpl->maArguments << rArgs[ 1 ];
419     }
420     catch( Exception& )
421     {
422     }
423 
424     if (rArgs.hasElements())
425     {
426         Sequence<css::beans::PropertyValue> aSeq;
427         rArgs[0] >>= aSeq;
428         for (const auto& rVal : std::as_const(aSeq))
429         {
430             if (rVal.Name == "UserData")
431             {
432                 css::uno::Sequence<OUString> aUserDataSeq;
433                 rVal.Value >>= aUserDataSeq;
434                 if (comphelper::findValue(aUserDataSeq, "macro-enabled") != -1)
435                     mxImpl->mbExportVBA = true;
436             }
437             else if (rVal.Name == "Flags")
438             {
439                 sal_Int32 nFlags(0);
440                 rVal.Value >>= nFlags;
441                 mxImpl->mbExportTemplate = bool(static_cast<SfxFilterFlags>(nFlags) & SfxFilterFlags::TEMPLATE);
442             }
443         }
444     }
445 }
446 
447 // com.sun.star.document.XImporter interface
448 
449 void SAL_CALL FilterBase::setTargetDocument( const Reference< XComponent >& rxDocument )
450 {
451     mxImpl->setDocumentModel( rxDocument );
452     mxImpl->meDirection = FILTERDIRECTION_IMPORT;
453 }
454 
455 // com.sun.star.document.XExporter interface
456 
457 void SAL_CALL FilterBase::setSourceDocument( const Reference< XComponent >& rxDocument )
458 {
459     mxImpl->setDocumentModel( rxDocument );
460     mxImpl->meDirection = FILTERDIRECTION_EXPORT;
461 }
462 
463 // com.sun.star.document.XFilter interface
464 
465 sal_Bool SAL_CALL FilterBase::filter( const Sequence< PropertyValue >& rMediaDescSeq )
466 {
467     if( !mxImpl->mxModel.is() || !mxImpl->mxModelFactory.is() || (mxImpl->meDirection == FILTERDIRECTION_UNKNOWN) )
468         throw RuntimeException();
469 
470     bool bRet = false;
471     setMediaDescriptor( rMediaDescSeq );
472     DocumentOpenedGuard aOpenedGuard( mxImpl->maFileUrl );
473     if( aOpenedGuard.isValid() || mxImpl->maFileUrl.isEmpty() )
474     {
475         mxImpl->initializeFilter();
476         switch( mxImpl->meDirection )
477         {
478             case FILTERDIRECTION_UNKNOWN:
479             break;
480             case FILTERDIRECTION_IMPORT:
481                 if( mxImpl->mxInStream.is() )
482                 {
483                     mxImpl->mxStorage = implCreateStorage( mxImpl->mxInStream );
484                     bRet = mxImpl->mxStorage.get() && importDocument();
485                 }
486             break;
487             case FILTERDIRECTION_EXPORT:
488                 if( mxImpl->mxOutStream.is() )
489                 {
490                     mxImpl->mxStorage = implCreateStorage( mxImpl->mxOutStream );
491                     bRet = mxImpl->mxStorage.get() && exportDocument() && implFinalizeExport( getMediaDescriptor() );
492                 }
493             break;
494         }
495         mxImpl->mxModel->unlockControllers();
496     }
497     return bRet;
498 }
499 
500 void SAL_CALL FilterBase::cancel()
501 {
502 }
503 
504 // protected
505 
506 Reference< XInputStream > FilterBase::implGetInputStream( MediaDescriptor& rMediaDesc ) const
507 {
508     return rMediaDesc.getUnpackedValueOrDefault( MediaDescriptor::PROP_INPUTSTREAM(), Reference< XInputStream >() );
509 }
510 
511 Reference< XStream > FilterBase::implGetOutputStream( MediaDescriptor& rMediaDesc ) const
512 {
513     return rMediaDesc.getUnpackedValueOrDefault( MediaDescriptor::PROP_STREAMFOROUTPUT(), Reference< XStream >() );
514 }
515 
516 bool FilterBase::implFinalizeExport( MediaDescriptor& /*rMediaDescriptor*/ )
517 {
518     return true;
519 }
520 
521 Reference< XStream > const & FilterBase::getMainDocumentStream( ) const
522 {
523     return mxImpl->mxOutStream;
524 }
525 
526 // private
527 
528 void FilterBase::setMediaDescriptor( const Sequence< PropertyValue >& rMediaDescSeq )
529 {
530     mxImpl->maMediaDesc << rMediaDescSeq;
531 
532     switch( mxImpl->meDirection )
533     {
534         case FILTERDIRECTION_UNKNOWN:
535             OSL_FAIL( "FilterBase::setMediaDescriptor - invalid filter direction" );
536         break;
537         case FILTERDIRECTION_IMPORT:
538             mxImpl->maMediaDesc.addInputStream();
539             mxImpl->mxInStream = implGetInputStream( mxImpl->maMediaDesc );
540             OSL_ENSURE( mxImpl->mxInStream.is(), "FilterBase::setMediaDescriptor - missing input stream" );
541         break;
542         case FILTERDIRECTION_EXPORT:
543             mxImpl->mxOutStream = implGetOutputStream( mxImpl->maMediaDesc );
544             OSL_ENSURE( mxImpl->mxOutStream.is(), "FilterBase::setMediaDescriptor - missing output stream" );
545         break;
546     }
547 
548     mxImpl->maFileUrl = mxImpl->maMediaDesc.getUnpackedValueOrDefault( MediaDescriptor::PROP_URL(), OUString() );
549     mxImpl->mxTargetFrame = mxImpl->maMediaDesc.getUnpackedValueOrDefault( MediaDescriptor::PROP_FRAME(), Reference< XFrame >() );
550     mxImpl->mxStatusIndicator = mxImpl->maMediaDesc.getUnpackedValueOrDefault( MediaDescriptor::PROP_STATUSINDICATOR(), Reference< XStatusIndicator >() );
551     mxImpl->mxInteractionHandler = mxImpl->maMediaDesc.getUnpackedValueOrDefault( MediaDescriptor::PROP_INTERACTIONHANDLER(), Reference< XInteractionHandler >() );
552     mxImpl->mxParentShape = mxImpl->maMediaDesc.getUnpackedValueOrDefault( "ParentShape", mxImpl->mxParentShape );
553     mxImpl->maFilterData = mxImpl->maMediaDesc.getUnpackedValueOrDefault( "FilterData", Sequence< PropertyValue >() );
554 
555     // Check for ISO OOXML
556     OUString sFilterName = mxImpl->maMediaDesc.getUnpackedValueOrDefault( "FilterName", OUString() );
557     try
558     {
559         Reference<XMultiServiceFactory> xFactory(getComponentContext()->getServiceManager(), UNO_QUERY_THROW);
560         Reference<XNameAccess> xFilters(xFactory->createInstance("com.sun.star.document.FilterFactory" ), UNO_QUERY_THROW );
561         Any aValues = xFilters->getByName( sFilterName );
562         Sequence<PropertyValue > aPropSeq;
563         aValues >>= aPropSeq;
564         SequenceAsHashMap aProps( aPropSeq );
565 
566         sal_Int32 nVersion = aProps.getUnpackedValueOrDefault( "FileFormatVersion", sal_Int32( 0 ) );
567         mxImpl->meVersion = OoxmlVersion( nVersion );
568     }
569     catch ( const Exception& )
570     {
571         // Not ISO OOXML
572     }
573 }
574 
575 GraphicHelper* FilterBase::implCreateGraphicHelper() const
576 {
577     // default: return base implementation without any special behaviour
578     return new GraphicHelper( mxImpl->mxComponentContext, mxImpl->mxTargetFrame, mxImpl->mxStorage );
579 }
580 
581 bool FilterBase::exportVBA() const
582 {
583     return mxImpl->mbExportVBA;
584 }
585 
586 bool FilterBase::isExportTemplate() const
587 {
588     return mxImpl->mbExportTemplate;
589 }
590 
591 } // namespace core
592 } // namespace oox
593 
594 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
595