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
