diff options
Diffstat (limited to '')
-rw-r--r-- | sfx2/source/doc/docfile.cxx | 4752 |
1 files changed, 4752 insertions, 0 deletions
diff --git a/sfx2/source/doc/docfile.cxx b/sfx2/source/doc/docfile.cxx new file mode 100644 index 000000000..fafe05d15 --- /dev/null +++ b/sfx2/source/doc/docfile.cxx @@ -0,0 +1,4752 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_features.h> + +#ifdef UNX +#include <sys/stat.h> +#endif + +#include <sfx2/docfile.hxx> +#include <sfx2/signaturestate.hxx> + +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/ucb/XContent.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/document/XDocumentRevisionListPersistence.hpp> +#include <com/sun/star/document/LockedDocumentRequest.hpp> +#include <com/sun/star/document/LockedOnSavingRequest.hpp> +#include <com/sun/star/document/OwnLockOnDocumentRequest.hpp> +#include <com/sun/star/document/LockFileIgnoreRequest.hpp> +#include <com/sun/star/document/LockFileCorruptRequest.hpp> +#include <com/sun/star/document/ChangedByOthersRequest.hpp> +#include <com/sun/star/document/ReloadEditableRequest.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/UseBackupException.hpp> +#include <com/sun/star/embed/XOptimizedStorage.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/frame/XTerminateListener.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/InteractiveIOException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/InteractiveLockingLockedException.hpp> +#include <com/sun/star/ucb/InteractiveNetworkWriteException.hpp> +#include <com/sun/star/ucb/Lock.hpp> +#include <com/sun/star/ucb/NameClashException.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/XProgressHandler.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XTruncate.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/ucb/InsertCommandArgument.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/security/DocumentDigitalSignatures.hpp> +#include <com/sun/star/security/XCertificate.hpp> +#include <tools/urlobj.hxx> +#include <tools/fileutil.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/tempfile.hxx> +#include <comphelper/fileurl.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/interaction.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/simplefileaccessinteraction.hxx> +#include <comphelper/string.hxx> +#include <framework/interaction.hxx> +#include <utility> +#include <svl/stritem.hxx> +#include <svl/eitem.hxx> +#include <svtools/sfxecode.hxx> +#include <svl/itemset.hxx> +#include <svl/intitem.hxx> +#include <svtools/svparser.hxx> +#include <sal/log.hxx> + +#include <unotools/streamwrap.hxx> + +#include <osl/file.hxx> + +#include <comphelper/storagehelper.hxx> +#include <unotools/mediadescriptor.hxx> +#include <comphelper/docpasswordhelper.hxx> +#include <tools/datetime.hxx> +#include <unotools/pathoptions.hxx> +#include <svtools/asynclink.hxx> +#include <ucbhelper/commandenvironment.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <unotools/ucbhelper.hxx> +#include <unotools/progresshandlerwrap.hxx> +#include <ucbhelper/content.hxx> +#include <ucbhelper/interactionrequest.hxx> +#include <sot/storage.hxx> +#include <svl/documentlockfile.hxx> +#include <svl/msodocumentlockfile.hxx> +#include <com/sun/star/document/DocumentRevisionListPersistence.hpp> + +#include <sfx2/app.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/fcontnr.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/sfxuno.hxx> +#include <openflag.hxx> +#include <officecfg/Office/Common.hxx> +#include <comphelper/propertysequence.hxx> +#include <vcl/weld.hxx> +#include <vcl/svapp.hxx> +#include <tools/diagnose_ex.h> +#include <unotools/fltrcfg.hxx> +#include <sfx2/digitalsignatures.hxx> +#include <sfx2/viewfrm.hxx> +#include <comphelper/threadpool.hxx> +#include <o3tl/string_view.hxx> +#include <condition_variable> + +#include <com/sun/star/io/WrongFormatException.hpp> + +#include <memory> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::graphic; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::security; + +namespace +{ + +struct ReadOnlyMediumEntry +{ + ReadOnlyMediumEntry(std::shared_ptr<std::recursive_mutex> pMutex, + std::shared_ptr<bool> pIsDestructed) + : _pMutex(pMutex) + , _pIsDestructed(pIsDestructed) + { + } + std::shared_ptr<std::recursive_mutex> _pMutex; + std::shared_ptr<bool> _pIsDestructed; +}; + +} + +static std::mutex g_chkReadOnlyGlobalMutex; +static bool g_bChkReadOnlyTaskRunning = false; +static std::unordered_map<SfxMedium*, std::shared_ptr<ReadOnlyMediumEntry>> g_newReadOnlyDocs; +static std::unordered_map<SfxMedium*, std::shared_ptr<ReadOnlyMediumEntry>> g_existingReadOnlyDocs; + +namespace { + +#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT + +bool IsSystemFileLockingUsed() +{ +#if HAVE_FEATURE_MACOSX_SANDBOX + return true; +#else + return officecfg::Office::Common::Misc::UseDocumentSystemFileLocking::get(); +#endif +} + + +bool IsOOoLockFileUsed() +{ +#if HAVE_FEATURE_MACOSX_SANDBOX + return false; +#else + return officecfg::Office::Common::Misc::UseDocumentOOoLockFile::get(); +#endif +} + +bool IsLockingUsed() +{ + return officecfg::Office::Common::Misc::UseLocking::get(); +} + +#endif + +#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT +bool IsWebDAVLockingUsed() +{ + return officecfg::Office::Common::Misc::UseWebDAVFileLocking::get(); +} +#endif + +/// Gets default attributes of a file:// URL. +sal_uInt64 GetDefaultFileAttributes(const OUString& rURL) +{ + sal_uInt64 nRet = 0; + + if (!comphelper::isFileUrl(rURL)) + return nRet; + + // Make sure the file exists (and create it if not). + osl::File aFile(rURL); + osl::File::RC nRes = aFile.open(osl_File_OpenFlag_Create); + if (nRes != osl::File::E_None && nRes != osl::File::E_EXIST) + return nRet; + + aFile.close(); + + osl::DirectoryItem aItem; + if (osl::DirectoryItem::get(rURL, aItem) != osl::DirectoryItem::E_None) + return nRet; + + osl::FileStatus aStatus(osl_FileStatus_Mask_Attributes); + if (aItem.getFileStatus(aStatus) != osl::DirectoryItem::E_None) + return nRet; + + nRet = aStatus.getAttributes(); + return nRet; +} + +/// Determines if rURL is safe to move or not. +bool IsFileMovable(const INetURLObject& rURL) +{ +#ifdef MACOSX + (void)rURL; + // Hide extension macOS-specific file property would be lost. + return false; +#else + + if (rURL.GetProtocol() != INetProtocol::File) + // Not a file:// URL. + return false; + +#ifdef UNX + OUString sPath = rURL.getFSysPath(FSysStyle::Unix); + if (sPath.isEmpty()) + return false; + + struct stat buf; + if (lstat(sPath.toUtf8().getStr(), &buf) != 0) + return false; + + // Hardlink or symlink: osl::File::move() doesn't play with these nicely. + if (buf.st_nlink > 1 || S_ISLNK(buf.st_mode)) + return false; +#elif defined _WIN32 + if (tools::IsMappedWebDAVPath(rURL.GetMainURL(INetURLObject::DecodeMechanism::NONE))) + return false; +#endif + + return true; +#endif +} + +class CheckReadOnlyTaskTerminateListener + : public ::cppu::WeakImplHelper<css::frame::XTerminateListener> +{ +public: + // XEventListener + void SAL_CALL disposing(const css::lang::EventObject& Source) override; + + // XTerminateListener + void SAL_CALL queryTermination(const css::lang::EventObject& aEvent) override; + void SAL_CALL notifyTermination(const css::lang::EventObject& aEvent) override; + + bool bIsTerminated = false; + std::condition_variable mCond; + std::mutex mMutex; +}; + +class CheckReadOnlyTask : public comphelper::ThreadTask +{ +public: + CheckReadOnlyTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag); + ~CheckReadOnlyTask(); + + virtual void doWork() override; + +private: + rtl::Reference<CheckReadOnlyTaskTerminateListener> m_xListener; +}; + +} // anonymous namespace + +CheckReadOnlyTask::CheckReadOnlyTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag) + : ThreadTask(pTag) + , m_xListener(new CheckReadOnlyTaskTerminateListener) +{ + Reference<css::frame::XDesktop> xDesktop + = css::frame::Desktop::create(comphelper::getProcessComponentContext()); + if (xDesktop.is() && m_xListener != nullptr) + { + xDesktop->addTerminateListener(m_xListener); + } +} + +CheckReadOnlyTask::~CheckReadOnlyTask() +{ + Reference<css::frame::XDesktop> xDesktop + = css::frame::Desktop::create(comphelper::getProcessComponentContext()); + if (xDesktop.is() && m_xListener != nullptr) + { + std::unique_lock<std::mutex> lock(m_xListener->mMutex); + if (!m_xListener->bIsTerminated) + { + lock.unlock(); + xDesktop->removeTerminateListener(m_xListener); + } + } +} + +namespace +{ +void SAL_CALL +CheckReadOnlyTaskTerminateListener::disposing(const css::lang::EventObject& /*Source*/) +{ +} + +void SAL_CALL +CheckReadOnlyTaskTerminateListener::queryTermination(const css::lang::EventObject& /*aEvent*/) +{ +} + +void SAL_CALL +CheckReadOnlyTaskTerminateListener::notifyTermination(const css::lang::EventObject& /*aEvent*/) +{ + std::unique_lock<std::mutex> lock(mMutex); + bIsTerminated = true; + lock.unlock(); + mCond.notify_one(); +} +} + +class SfxMedium_Impl +{ +public: + StreamMode m_nStorOpenMode; + ErrCode m_eError; + + ::ucbhelper::Content aContent; + bool bUpdatePickList:1; + bool bIsTemp:1; + bool bDownloadDone:1; + bool bIsStorage:1; + bool bUseInteractionHandler:1; + bool bAllowDefaultIntHdl:1; + bool bDisposeStorage:1; + bool bStorageBasedOnInStream:1; + bool m_bSalvageMode:1; + bool m_bVersionsAlreadyLoaded:1; + bool m_bLocked:1; + bool m_bMSOLockFileCreated : 1; + bool m_bDisableUnlockWebDAV:1; + bool m_bGotDateTime:1; + bool m_bRemoveBackup:1; + bool m_bOriginallyReadOnly:1; + bool m_bOriginallyLoadedReadOnly:1; + bool m_bTriedStorage:1; + bool m_bRemote:1; + bool m_bInputStreamIsReadOnly:1; + bool m_bInCheckIn:1; + bool m_bDisableFileSync = false; + bool m_bNotifyWhenEditable = false; + + OUString m_aName; + OUString m_aLogicName; + OUString m_aLongName; + + mutable std::shared_ptr<SfxItemSet> m_pSet; + mutable std::unique_ptr<INetURLObject> m_pURLObj; + + std::shared_ptr<const SfxFilter> m_pFilter; + std::shared_ptr<const SfxFilter> m_pCustomFilter; + + std::shared_ptr<std::recursive_mutex> m_pCheckEditableWorkerMutex; + std::shared_ptr<bool> m_pIsDestructed; + ImplSVEvent* m_pReloadEvent; + + std::unique_ptr<SvStream> m_pInStream; + std::unique_ptr<SvStream> m_pOutStream; + + OUString aOrigURL; + DateTime aExpireTime; + SfxFrameWeakRef wLoadTargetFrame; + SvKeyValueIteratorRef xAttributes; + + svtools::AsynchronLink aDoneLink; + + uno::Sequence < util::RevisionTag > aVersions; + + std::unique_ptr<::utl::TempFile> pTempFile; + + uno::Reference<embed::XStorage> xStorage; + uno::Reference<embed::XStorage> m_xZipStorage; + uno::Reference<io::XInputStream> m_xInputStreamToLoadFrom; + uno::Reference<io::XInputStream> xInputStream; + uno::Reference<io::XStream> xStream; + uno::Reference<io::XStream> m_xLockingStream; + uno::Reference<task::XInteractionHandler> xInteraction; + + ErrCode nLastStorageError; + + OUString m_aBackupURL; + + // the following member is changed and makes sense only during saving + // TODO/LATER: in future the signature state should be controlled by the medium not by the document + // in this case the member will hold this information + SignatureState m_nSignatureState; + + bool m_bHasEmbeddedObjects = false; + + util::DateTime m_aDateTime; + + uno::Sequence<beans::PropertyValue> m_aArgs; + + explicit SfxMedium_Impl(); + ~SfxMedium_Impl(); + SfxMedium_Impl(const SfxMedium_Impl&) = delete; + SfxMedium_Impl& operator=(const SfxMedium_Impl&) = delete; + + OUString getFilterMimeType() const + { return !m_pFilter ? OUString() : m_pFilter->GetMimeType(); } +}; + +SfxMedium_Impl::SfxMedium_Impl() : + m_nStorOpenMode(SFX_STREAM_READWRITE), + m_eError(ERRCODE_NONE), + bUpdatePickList(true), + bIsTemp( false ), + bDownloadDone( true ), + bIsStorage( false ), + bUseInteractionHandler( true ), + bAllowDefaultIntHdl( false ), + bDisposeStorage( false ), + bStorageBasedOnInStream( false ), + m_bSalvageMode( false ), + m_bVersionsAlreadyLoaded( false ), + m_bLocked( false ), + m_bMSOLockFileCreated( false ), + m_bDisableUnlockWebDAV( false ), + m_bGotDateTime( false ), + m_bRemoveBackup( false ), + m_bOriginallyReadOnly(false), + m_bOriginallyLoadedReadOnly(false), + m_bTriedStorage(false), + m_bRemote(false), + m_bInputStreamIsReadOnly(false), + m_bInCheckIn(false), + m_pReloadEvent(nullptr), + aExpireTime( DateTime( DateTime::SYSTEM ) + static_cast<sal_Int32>(10) ), + nLastStorageError( ERRCODE_NONE ), + m_nSignatureState( SignatureState::NOSIGNATURES ) +{ +} + + +SfxMedium_Impl::~SfxMedium_Impl() +{ + aDoneLink.ClearPendingCall(); + + pTempFile.reset(); + m_pSet.reset(); + std::unique_lock<std::recursive_mutex> chkEditLock; + if (m_pCheckEditableWorkerMutex != nullptr) + chkEditLock = std::unique_lock<std::recursive_mutex>(*m_pCheckEditableWorkerMutex); + m_pURLObj.reset(); +} + +void SfxMedium::ResetError() +{ + pImpl->m_eError = ERRCODE_NONE; + if( pImpl->m_pInStream ) + pImpl->m_pInStream->ResetError(); + if( pImpl->m_pOutStream ) + pImpl->m_pOutStream->ResetError(); +} + +ErrCode const & SfxMedium::GetLastStorageCreationState() const +{ + return pImpl->nLastStorageError; +} + +void SfxMedium::SetError(ErrCode nError) +{ + pImpl->m_eError = nError; +} + +ErrCode SfxMedium::GetErrorCode() const +{ + ErrCode lError = pImpl->m_eError; + if(!lError && pImpl->m_pInStream) + lError = pImpl->m_pInStream->GetErrorCode(); + if(!lError && pImpl->m_pOutStream) + lError = pImpl->m_pOutStream->GetErrorCode(); + return lError; +} + +void SfxMedium::CheckFileDate( const util::DateTime& aInitDate ) +{ + GetInitFileDate( true ); + if ( pImpl->m_aDateTime.Seconds == aInitDate.Seconds + && pImpl->m_aDateTime.Minutes == aInitDate.Minutes + && pImpl->m_aDateTime.Hours == aInitDate.Hours + && pImpl->m_aDateTime.Day == aInitDate.Day + && pImpl->m_aDateTime.Month == aInitDate.Month + && pImpl->m_aDateTime.Year == aInitDate.Year ) + return; + + uno::Reference< task::XInteractionHandler > xHandler = GetInteractionHandler(); + + if ( !xHandler.is() ) + return; + + try + { + ::rtl::Reference< ::ucbhelper::InteractionRequest > xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::Any( + document::ChangedByOthersRequest() ) ); + uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations{ + new ::ucbhelper::InteractionAbort( xInteractionRequestImpl.get() ), + new ::ucbhelper::InteractionApprove( xInteractionRequestImpl.get() ) + }; + xInteractionRequestImpl->setContinuations( aContinuations ); + + xHandler->handle( xInteractionRequestImpl ); + + ::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = xInteractionRequestImpl->getSelection(); + if ( uno::Reference< task::XInteractionAbort >( xSelected.get(), uno::UNO_QUERY ).is() ) + { + SetError(ERRCODE_ABORT); + } + } + catch ( const uno::Exception& ) + {} +} + +bool SfxMedium::DocNeedsFileDateCheck() const +{ + return ( !IsReadOnly() && ( GetURLObject().GetProtocol() == INetProtocol::File || + GetURLObject().isAnyKnownWebDAVScheme() ) ); +} + +util::DateTime const & SfxMedium::GetInitFileDate( bool bIgnoreOldValue ) +{ + if ( ( bIgnoreOldValue || !pImpl->m_bGotDateTime ) && !pImpl->m_aLogicName.isEmpty() ) + { + try + { + // add a default css::ucb::XCommandEnvironment + // in order to have the WebDAV UCP provider manage http/https authentication correctly + ::ucbhelper::Content aContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), + utl::UCBContentHelper::getDefaultCommandEnvironment(), + comphelper::getProcessComponentContext() ); + + aContent.getPropertyValue("DateModified") >>= pImpl->m_aDateTime; + pImpl->m_bGotDateTime = true; + } + catch ( const css::uno::Exception& ) + { + } + } + + return pImpl->m_aDateTime; +} + + +Reference < XContent > SfxMedium::GetContent() const +{ + if ( !pImpl->aContent.get().is() ) + { + Reference < css::ucb::XContent > xContent; + + // tdf#95144 add a default css::ucb::XCommandEnvironment + // in order to have the WebDAV UCP provider manage https protocol certificates correctly + css:: uno::Reference< task::XInteractionHandler > xIH( + css::task::InteractionHandler::createWithParent( comphelper::getProcessComponentContext(), nullptr ) ); + + css::uno::Reference< css::ucb::XProgressHandler > xProgress; + rtl::Reference<::ucbhelper::CommandEnvironment> pCommandEnv = new ::ucbhelper::CommandEnvironment( new comphelper::SimpleFileAccessInteraction( xIH ), xProgress ); + + const SfxUnoAnyItem* pItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_CONTENT, false); + if ( pItem ) + pItem->GetValue() >>= xContent; + + if ( xContent.is() ) + { + try + { + pImpl->aContent = ::ucbhelper::Content( xContent, pCommandEnv, comphelper::getProcessComponentContext() ); + } + catch ( const Exception& ) + { + } + } + else + { + // TODO: SAL_WARN( "sfx.doc", "SfxMedium::GetContent()\nCreate Content? This code exists as fallback only. Please clarify, why it's used."); + OUString aURL; + if ( !pImpl->m_aName.isEmpty() ) + osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aURL ); + else if ( !pImpl->m_aLogicName.isEmpty() ) + aURL = GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ); + if (!aURL.isEmpty() ) + (void)::ucbhelper::Content::create( aURL, pCommandEnv, comphelper::getProcessComponentContext(), pImpl->aContent ); + } + } + + return pImpl->aContent.get(); +} + +OUString SfxMedium::GetBaseURL( bool bForSaving ) +{ + OUString aBaseURL; + const SfxStringItem* pBaseURLItem = GetItemSet()->GetItem<SfxStringItem>(SID_DOC_BASEURL); + if ( pBaseURLItem ) + aBaseURL = pBaseURLItem->GetValue(); + else if (!utl::ConfigManager::IsFuzzing() && GetContent().is()) + { + try + { + Any aAny = pImpl->aContent.getPropertyValue("BaseURI"); + aAny >>= aBaseURL; + } + catch ( const css::uno::Exception& ) + { + } + + if ( aBaseURL.isEmpty() ) + aBaseURL = GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + + if ( bForSaving ) + { + bool bIsRemote = IsRemote(); + if( (bIsRemote && !officecfg::Office::Common::Save::URL::Internet::get()) + || (!pImpl->m_bRemote && !officecfg::Office::Common::Save::URL::FileSystem::get()) ) + return OUString(); + } + + return aBaseURL; +} + +bool SfxMedium::IsSkipImages() const +{ + const SfxStringItem* pSkipImagesItem = GetItemSet()->GetItem<SfxStringItem>(SID_FILE_FILTEROPTIONS); + return pSkipImagesItem && pSkipImagesItem->GetValue() == "SkipImages"; +} + +SvStream* SfxMedium::GetInStream() +{ + if ( pImpl->m_pInStream ) + return pImpl->m_pInStream.get(); + + if ( pImpl->pTempFile ) + { + pImpl->m_pInStream.reset( new SvFileStream(pImpl->m_aName, pImpl->m_nStorOpenMode) ); + + pImpl->m_eError = pImpl->m_pInStream->GetError(); + + if (!pImpl->m_eError && (pImpl->m_nStorOpenMode & StreamMode::WRITE) + && ! pImpl->m_pInStream->IsWritable() ) + { + pImpl->m_eError = ERRCODE_IO_ACCESSDENIED; + pImpl->m_pInStream.reset(); + } + else + return pImpl->m_pInStream.get(); + } + + GetMedium_Impl(); + + if ( GetError() ) + return nullptr; + + return pImpl->m_pInStream.get(); +} + + +void SfxMedium::CloseInStream() +{ + CloseInStream_Impl(); +} + +void SfxMedium::CloseInStream_Impl(bool bInDestruction) +{ + // if there is a storage based on the InStream, we have to + // close the storage, too, because otherwise the storage + // would use an invalid ( deleted ) stream. + if ( pImpl->m_pInStream && pImpl->xStorage.is() ) + { + if ( pImpl->bStorageBasedOnInStream ) + CloseStorage(); + } + + if ( pImpl->m_pInStream && !GetContent().is() && !bInDestruction ) + { + CreateTempFile(); + return; + } + + pImpl->m_pInStream.reset(); + if ( pImpl->m_pSet ) + pImpl->m_pSet->ClearItem( SID_INPUTSTREAM ); + + CloseZipStorage_Impl(); + pImpl->xInputStream.clear(); + + if ( !pImpl->m_pOutStream ) + { + // output part of the stream is not used so the whole stream can be closed + // TODO/LATER: is it correct? + pImpl->xStream.clear(); + if ( pImpl->m_pSet ) + pImpl->m_pSet->ClearItem( SID_STREAM ); + } +} + + +SvStream* SfxMedium::GetOutStream() +{ + if ( !pImpl->m_pOutStream ) + { + // Create a temp. file if there is none because we always + // need one. + CreateTempFile( false ); + + if ( pImpl->pTempFile ) + { + // On windows we try to re-use XOutStream from xStream if that exists; + // because opening new SvFileStream in this situation may fail with ERROR_SHARING_VIOLATION + // TODO: this is a horrible hack that should probably be removed, + // somebody needs to investigate this more thoroughly... + if (getenv("SFX_MEDIUM_REUSE_STREAM") && pImpl->xStream.is()) + { + assert(pImpl->xStream->getOutputStream().is()); // need that... + pImpl->m_pOutStream = utl::UcbStreamHelper::CreateStream( + pImpl->xStream, false); + } + else + { + // On Unix don't try to re-use XOutStream from xStream if that exists; + // it causes fdo#59022 (fails opening files via SMB on Linux) + pImpl->m_pOutStream.reset( new SvFileStream( + pImpl->m_aName, StreamMode::STD_READWRITE) ); + } + CloseStorage(); + } + } + + return pImpl->m_pOutStream.get(); +} + + +void SfxMedium::CloseOutStream() +{ + CloseOutStream_Impl(); +} + +void SfxMedium::CloseOutStream_Impl() +{ + if ( pImpl->m_pOutStream ) + { + // if there is a storage based on the OutStream, we have to + // close the storage, too, because otherwise the storage + // would use an invalid ( deleted ) stream. + //TODO/MBA: how to deal with this?! + //maybe we need a new flag when the storage was created from the outstream + if ( pImpl->xStorage.is() ) + { + CloseStorage(); + } + + pImpl->m_pOutStream.reset(); + } + + if ( !pImpl->m_pInStream ) + { + // input part of the stream is not used so the whole stream can be closed + // TODO/LATER: is it correct? + pImpl->xStream.clear(); + if ( pImpl->m_pSet ) + pImpl->m_pSet->ClearItem( SID_STREAM ); + } +} + + +const OUString& SfxMedium::GetPhysicalName() const +{ + if ( pImpl->m_aName.isEmpty() && !pImpl->m_aLogicName.isEmpty() ) + const_cast<SfxMedium*>(this)->CreateFileStream(); + + // return the name then + return pImpl->m_aName; +} + + +void SfxMedium::CreateFileStream() +{ + // force synchron + if( pImpl->m_pInStream ) + { + SvLockBytes* pBytes = pImpl->m_pInStream->GetLockBytes(); + if( pBytes ) + pBytes->SetSynchronMode(); + } + + GetInStream(); + if( pImpl->m_pInStream ) + { + CreateTempFile( false ); + pImpl->bIsTemp = true; + CloseInStream_Impl(); + } +} + + +bool SfxMedium::Commit() +{ + if( pImpl->xStorage.is() ) + StorageCommit_Impl(); + else if( pImpl->m_pOutStream ) + pImpl->m_pOutStream->FlushBuffer(); + else if( pImpl->m_pInStream ) + pImpl->m_pInStream->FlushBuffer(); + + if ( GetError() == ERRCODE_NONE ) + { + // does something only in case there is a temporary file ( means aName points to different location than aLogicName ) + Transfer_Impl(); + } + + bool bResult = ( GetError() == ERRCODE_NONE ); + + if ( bResult && DocNeedsFileDateCheck() ) + GetInitFileDate( true ); + + // remove truncation mode from the flags + pImpl->m_nStorOpenMode &= ~StreamMode::TRUNC; + return bResult; +} + + +bool SfxMedium::IsStorage() +{ + if ( pImpl->xStorage.is() ) + return true; + + if ( pImpl->m_bTriedStorage ) + return pImpl->bIsStorage; + + if ( pImpl->pTempFile ) + { + OUString aURL; + if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aURL ) + != osl::FileBase::E_None ) + { + SAL_WARN( "sfx.doc", "Physical name '" << pImpl->m_aName << "' not convertible to file URL"); + } + pImpl->bIsStorage = SotStorage::IsStorageFile( aURL ) && !SotStorage::IsOLEStorage( aURL); + if ( !pImpl->bIsStorage ) + pImpl->m_bTriedStorage = true; + } + else if ( GetInStream() ) + { + pImpl->bIsStorage = SotStorage::IsStorageFile( pImpl->m_pInStream.get() ) && !SotStorage::IsOLEStorage( pImpl->m_pInStream.get() ); + if ( !pImpl->m_pInStream->GetError() && !pImpl->bIsStorage ) + pImpl->m_bTriedStorage = true; + } + + return pImpl->bIsStorage; +} + + +bool SfxMedium::IsPreview_Impl() const +{ + bool bPreview = false; + const SfxBoolItem* pPreview = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_PREVIEW, false); + if ( pPreview ) + bPreview = pPreview->GetValue(); + else + { + const SfxStringItem* pFlags = SfxItemSet::GetItem<SfxStringItem>(GetItemSet(), SID_OPTIONS, false); + if ( pFlags ) + { + OUString aFileFlags = pFlags->GetValue(); + aFileFlags = aFileFlags.toAsciiUpperCase(); + if ( -1 != aFileFlags.indexOf( 'B' ) ) + bPreview = true; + } + } + + return bPreview; +} + + +void SfxMedium::StorageBackup_Impl() +{ + ::ucbhelper::Content aOriginalContent; + Reference< css::ucb::XCommandEnvironment > xDummyEnv; + + bool bBasedOnOriginalFile = + !pImpl->pTempFile + && ( pImpl->m_aLogicName.isEmpty() || !pImpl->m_bSalvageMode ) + && !GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ).isEmpty() + && GetURLObject().GetProtocol() == INetProtocol::File + && ::utl::UCBContentHelper::IsDocument( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + + if ( bBasedOnOriginalFile && pImpl->m_aBackupURL.isEmpty() + && ::ucbhelper::Content::create( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext(), aOriginalContent ) ) + { + DoInternalBackup_Impl( aOriginalContent ); + if( pImpl->m_aBackupURL.isEmpty() ) + SetError(ERRCODE_SFX_CANTCREATEBACKUP); + } +} + + +OUString const & SfxMedium::GetBackup_Impl() +{ + if ( pImpl->m_aBackupURL.isEmpty() ) + StorageBackup_Impl(); + + return pImpl->m_aBackupURL; +} + + +uno::Reference < embed::XStorage > SfxMedium::GetOutputStorage() +{ + if ( GetError() ) + return uno::Reference< embed::XStorage >(); + + // if the medium was constructed with a Storage: use this one, not a temp. storage + // if a temporary storage already exists: use it + if ( pImpl->xStorage.is() && ( pImpl->m_aLogicName.isEmpty() || pImpl->pTempFile ) ) + return pImpl->xStorage; + + // if necessary close stream that was used for reading + if ( pImpl->m_pInStream && !pImpl->m_pInStream->IsWritable() ) + CloseInStream(); + + DBG_ASSERT( !pImpl->m_pOutStream, "OutStream in a readonly Medium?!" ); + + // TODO/LATER: The current solution is to store the document temporary and then copy it to the target location; + // in future it should be stored directly and then copied to the temporary location, since in this case no + // file attributes have to be preserved and system copying mechanics could be used instead of streaming. + CreateTempFileNoCopy(); + + return GetStorage(); +} + + +void SfxMedium::SetEncryptionDataToStorage_Impl() +{ + // in case media-descriptor contains password it should be used on opening + if ( !pImpl->xStorage.is() || !pImpl->m_pSet ) + return; + + uno::Sequence< beans::NamedValue > aEncryptionData; + if ( !GetEncryptionData_Impl( pImpl->m_pSet.get(), aEncryptionData ) ) + return; + + // replace the password with encryption data + pImpl->m_pSet->ClearItem( SID_PASSWORD ); + pImpl->m_pSet->Put( SfxUnoAnyItem( SID_ENCRYPTIONDATA, uno::Any( aEncryptionData ) ) ); + + try + { + ::comphelper::OStorageHelper::SetCommonStorageEncryptionData( pImpl->xStorage, aEncryptionData ); + } + catch( const uno::Exception& ) + { + SAL_WARN( "sfx.doc", "It must be possible to set a common password for the storage" ); + // TODO/LATER: set the error code in case of problem + // SetError(ERRCODE_IO_GENERAL); + } +} + +#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT + +// FIXME: Hmm actually lock files should be used for sftp: documents +// even if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT. Only the use of lock +// files for *local* documents is unnecessary in that case. But +// actually, the checks for sftp: here are just wishful thinking; I +// don't this there is any support for actually editing documents +// behind sftp: URLs anyway. + +// Sure, there could perhaps be a 3rd-party extension that brings UCB +// the potential to handle files behind sftp:. But there could also be +// an extension that handles some arbitrary foobar: scheme *and* it +// could be that lock files would be the correct thing to use for +// foobar: documents, too. But the hardcoded test below won't know +// that. Clearly the knowledge whether lock files should be used or +// not for some URL scheme belongs in UCB, not here. + +namespace +{ + +OUString tryMSOwnerFiles(std::u16string_view sDocURL) +{ + svt::MSODocumentLockFile aMSOLockFile(sDocURL); + LockFileEntry aData; + try + { + aData = aMSOLockFile.GetLockData(); + } + catch( const uno::Exception& ) + { + return OUString(); + } + + OUString sUserData = aData[LockFileComponent::OOOUSERNAME]; + + if (!sUserData.isEmpty()) + sUserData += " (MS Office)"; // Mention the used office suite + + return sUserData; +} + +OUString tryForeignLockfiles(std::u16string_view sDocURL) +{ + OUString sUserData = tryMSOwnerFiles(sDocURL); + // here we can test for empty result, and add other known applications' lockfile testing + return sUserData.trim(); +} +} + +SfxMedium::ShowLockResult SfxMedium::ShowLockedDocumentDialog(const LockFileEntry& aData, + bool bIsLoading, bool bOwnLock, + bool bHandleSysLocked) +{ + ShowLockResult nResult = ShowLockResult::NoLock; + + // tdf#92817: Simple check for empty lock file that needs to be deleted, when system locking is enabled + if( aData[LockFileComponent::OOOUSERNAME].isEmpty() && aData[LockFileComponent::SYSUSERNAME].isEmpty() && !bHandleSysLocked ) + bOwnLock=true; + + // show the interaction regarding the document opening + uno::Reference< task::XInteractionHandler > xHandler = GetInteractionHandler(); + + if ( xHandler.is() && ( bIsLoading || !bHandleSysLocked || bOwnLock ) ) + { + OUString aDocumentURL + = GetURLObject().GetLastName(INetURLObject::DecodeMechanism::WithCharset); + OUString aInfo; + ::rtl::Reference< ::ucbhelper::InteractionRequest > xInteractionRequestImpl; + + sal_Int32 nContinuations = 3; + + if ( bOwnLock ) + { + aInfo = aData[LockFileComponent::EDITTIME]; + + xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::Any( + document::OwnLockOnDocumentRequest( OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo, !bIsLoading ) ) ); + } + else + { + // Use a fourth continuation in case there's no filesystem lock: + // "Ignore lock file and open/replace the document" + if (!bHandleSysLocked) + nContinuations = 4; + + if ( !aData[LockFileComponent::OOOUSERNAME].isEmpty() ) + aInfo = aData[LockFileComponent::OOOUSERNAME]; + else + aInfo = aData[LockFileComponent::SYSUSERNAME]; + + if (aInfo.isEmpty() && !GetURLObject().isAnyKnownWebDAVScheme()) + // Try to get name of user who has locked the file using other applications + aInfo = tryForeignLockfiles( + GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::NONE)); + + if ( !aInfo.isEmpty() && !aData[LockFileComponent::EDITTIME].isEmpty() ) + aInfo += " ( " + aData[LockFileComponent::EDITTIME] + " )"; + + if (!bIsLoading) // so, !bHandleSysLocked + { + xInteractionRequestImpl = new ::ucbhelper::InteractionRequest(uno::Any( + document::LockedOnSavingRequest(OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo))); + // Currently, only the last "Retry" continuation (meaning ignore the lock and try overwriting) can be returned. + } + else /*logically therefore bIsLoading is set */ + { + xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::Any( + document::LockedDocumentRequest( OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo ) ) ); + } + } + + uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations(nContinuations); + auto pContinuations = aContinuations.getArray(); + pContinuations[0] = new ::ucbhelper::InteractionAbort( xInteractionRequestImpl.get() ); + pContinuations[1] = new ::ucbhelper::InteractionApprove( xInteractionRequestImpl.get() ); + pContinuations[2] = new ::ucbhelper::InteractionDisapprove( xInteractionRequestImpl.get() ); + if (nContinuations > 3) + { + // We use InteractionRetry to reflect that user wants to + // ignore the (stale?) alien lock file and open/overwrite the document + pContinuations[3] = new ::ucbhelper::InteractionRetry(xInteractionRequestImpl.get()); + } + xInteractionRequestImpl->setContinuations( aContinuations ); + + xHandler->handle( xInteractionRequestImpl ); + + bool bOpenReadOnly = false; + ::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = xInteractionRequestImpl->getSelection(); + if ( uno::Reference< task::XInteractionAbort >( xSelected.get(), uno::UNO_QUERY ).is() ) + { + SetError(ERRCODE_ABORT); + } + else if ( uno::Reference< task::XInteractionDisapprove >( xSelected.get(), uno::UNO_QUERY ).is() ) + { + // own lock on loading, user has selected to ignore the lock + // own lock on saving, user has selected to ignore the lock + // alien lock on loading, user has selected to edit a copy of document + // TODO/LATER: alien lock on saving, user has selected to do SaveAs to different location + if ( !bOwnLock ) // bIsLoading implied from outermost condition + { + // means that a copy of the document should be opened + GetItemSet()->Put( SfxBoolItem( SID_TEMPLATE, true ) ); + } + else + nResult = ShowLockResult::Succeeded; + } + else if (uno::Reference< task::XInteractionRetry >(xSelected.get(), uno::UNO_QUERY).is()) + { + // User decided to ignore the alien (stale?) lock file without filesystem lock + nResult = ShowLockResult::Succeeded; + } + else if (uno::Reference< task::XInteractionApprove >( xSelected.get(), uno::UNO_QUERY ).is()) + { + bOpenReadOnly = true; + } + else // user selected "Notify" + { + pImpl->m_bNotifyWhenEditable = true; + AddToCheckEditableWorkerList(); + bOpenReadOnly = true; + } + + if (bOpenReadOnly) + { + // own lock on loading, user has selected to open readonly + // own lock on saving, user has selected to open readonly + // alien lock on loading, user has selected to retry saving + // TODO/LATER: alien lock on saving, user has selected to retry saving + + if (bIsLoading) + GetItemSet()->Put(SfxBoolItem(SID_DOC_READONLY, true)); + else + nResult = ShowLockResult::Try; + } + } + else + { + if ( bIsLoading ) + { + // if no interaction handler is provided the default answer is open readonly + // that usually happens in case the document is loaded per API + // so the document must be opened readonly for backward compatibility + GetItemSet()->Put( SfxBoolItem( SID_DOC_READONLY, true ) ); + } + else + SetError(ERRCODE_IO_ACCESSDENIED); + + } + + return nResult; +} + +bool SfxMedium::ShowLockFileProblemDialog(MessageDlg nWhichDlg) +{ + // system file locking is not active, ask user whether he wants to open the document without any locking + uno::Reference< task::XInteractionHandler > xHandler = GetInteractionHandler(); + + if (xHandler.is()) + { + ::rtl::Reference< ::ucbhelper::InteractionRequest > xIgnoreRequestImpl; + + switch (nWhichDlg) + { + case MessageDlg::LockFileIgnore: + xIgnoreRequestImpl = new ::ucbhelper::InteractionRequest(uno::Any( document::LockFileIgnoreRequest() )); + break; + case MessageDlg::LockFileCorrupt: + xIgnoreRequestImpl = new ::ucbhelper::InteractionRequest(uno::Any( document::LockFileCorruptRequest() )); + break; + } + + uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations{ + new ::ucbhelper::InteractionAbort(xIgnoreRequestImpl.get()), + new ::ucbhelper::InteractionApprove(xIgnoreRequestImpl.get()) + }; + xIgnoreRequestImpl->setContinuations(aContinuations); + + xHandler->handle(xIgnoreRequestImpl); + + ::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = xIgnoreRequestImpl->getSelection(); + bool bReadOnly = true; + + if (uno::Reference<task::XInteractionAbort>(xSelected.get(), uno::UNO_QUERY).is()) + { + SetError(ERRCODE_ABORT); + bReadOnly = false; + } + else if (!uno::Reference<task::XInteractionApprove>(xSelected.get(), uno::UNO_QUERY).is()) + { + // user selected "Notify" + pImpl->m_bNotifyWhenEditable = true; + AddToCheckEditableWorkerList(); + } + + if (bReadOnly) + GetItemSet()->Put(SfxBoolItem(SID_DOC_READONLY, true)); + + return bReadOnly; + } + + return false; +} + +namespace +{ + bool isSuitableProtocolForLocking(const OUString & rLogicName) + { + INetURLObject aUrl( rLogicName ); + INetProtocol eProt = aUrl.GetProtocol(); +#if !HAVE_FEATURE_MACOSX_SANDBOX + if (eProt == INetProtocol::File) { + return true; + } +#endif + return eProt == INetProtocol::Smb || eProt == INetProtocol::Sftp; + } +} + +#endif // HAVE_FEATURE_MULTIUSER_ENVIRONMENT + +// sets SID_DOC_READONLY if the document cannot be opened for editing +// if user cancel the loading the ERROR_ABORT is set +SfxMedium::LockFileResult SfxMedium::LockOrigFileOnDemand(bool bLoading, bool bNoUI, + bool bTryIgnoreLockFile, + LockFileEntry* pLockData) +{ +#if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT + (void) bLoading; + (void) bNoUI; + (void) bTryIgnoreLockFile; + (void) pLockData; + return LockFileResult::Succeeded; +#else + LockFileResult eResult = LockFileResult::Failed; + + // check if path scheme is http:// or https:// + // may be this is better if used always, in Android and iOS as well? + // if this code should be always there, remember to move the relevant code in UnlockFile method as well ! + + if ( GetURLObject().isAnyKnownWebDAVScheme() ) + { + // do nothing if WebDAV locking is disabled + if (!IsWebDAVLockingUsed()) + return LockFileResult::Succeeded; + + { + bool bResult = pImpl->m_bLocked; + bool bIsTemplate = false; + // so, this is webdav stuff... + if ( !bResult ) + { + // no read-write access is necessary on loading if the document is explicitly opened as copy + const SfxBoolItem* pTemplateItem = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_TEMPLATE, false); + bIsTemplate = ( bLoading && pTemplateItem && pTemplateItem->GetValue() ); + } + + if ( !bIsTemplate && !bResult && !IsReadOnly() ) + { + ShowLockResult bUIStatus = ShowLockResult::NoLock; + do + { + if( !bResult ) + { + uno::Reference< task::XInteractionHandler > xCHandler = GetInteractionHandler( true ); + Reference< css::ucb::XCommandEnvironment > xComEnv = new ::ucbhelper::CommandEnvironment( + xCHandler, Reference< css::ucb::XProgressHandler >() ); + + ucbhelper::Content aContentToLock( + GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), + xComEnv, comphelper::getProcessComponentContext() ); + + try + { + aContentToLock.lock(); + bResult = true; + } + catch ( ucb::InteractiveLockingLockedException& ) + { + // received when the resource is already locked + if (!bNoUI || pLockData) + { + // get the lock owner, using a special ucb.webdav property + // the owner property retrieved here is what the other principal send the server + // when activating the lock. + // See http://tools.ietf.org/html/rfc4918#section-14.17 for details + LockFileEntry aLockData; + aLockData[LockFileComponent::OOOUSERNAME] = "Unknown user"; + // This solution works right when the LO user name and the WebDAV user + // name are the same. + // A better thing to do would be to obtain the 'real' WebDAV user name, + // but that's not possible from a WebDAV UCP provider client. + LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry(); + // use the current LO user name as the system name + aLockData[LockFileComponent::SYSUSERNAME] + = aOwnData[LockFileComponent::SYSUSERNAME]; + + uno::Sequence<css::ucb::Lock> aLocks; + // getting the property, send a PROPFIND to the server over the net + if ((aContentToLock.getPropertyValue("DAV:lockdiscovery") >>= aLocks) && aLocks.hasElements()) + { + // got at least a lock, show the owner of the first lock returned + css::ucb::Lock aLock = aLocks[0]; + OUString aOwner; + if (aLock.Owner >>= aOwner) + { + // we need to display the WebDAV user name owning the lock, not the local one + aLockData[LockFileComponent::OOOUSERNAME] = aOwner; + } + } + + if (!bNoUI) + { + bUIStatus = ShowLockedDocumentDialog(aLockData, bLoading, false, + true); + } + + if (pLockData) + { + std::copy(aLockData.begin(), aLockData.end(), pLockData->begin()); + } + } + } + catch( ucb::InteractiveNetworkWriteException& ) + { + // This catch it's not really needed, here just for the sake of documentation on the behaviour. + // This is the most likely reason: + // - the remote site is a WebDAV with special configuration: read/only for read operations + // and read/write for write operations, the user is not allowed to lock/write and + // she cancelled the credentials request. + // this is not actually an error, but the exception is sent directly from ucb, avoiding the automatic + // management that takes part in cancelCommandExecution() + // Unfortunately there is no InteractiveNetwork*Exception available to signal this more correctly + // since it mostly happens on read/only part of webdav, this can be the most correct + // exception available + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "sfx.doc", "Locking exception: WebDAV while trying to lock the file" ); + } + } + } while( !bResult && bUIStatus == ShowLockResult::Try ); + } + + pImpl->m_bLocked = bResult; + + if ( !bResult && GetError() == ERRCODE_NONE ) + { + // the error should be set in case it is storing process + // or the document has been opened for editing explicitly + const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false); + + if ( !bLoading || (pReadOnlyItem && !pReadOnlyItem->GetValue()) ) + SetError(ERRCODE_IO_ACCESSDENIED); + else + GetItemSet()->Put( SfxBoolItem( SID_DOC_READONLY, true ) ); + } + + // when the file is locked, get the current file date + if ( bResult && DocNeedsFileDateCheck() ) + GetInitFileDate( true ); + + if ( bResult ) + eResult = LockFileResult::Succeeded; + } + return eResult; + } + + if (!IsLockingUsed()) + return LockFileResult::Succeeded; + if (GetURLObject().HasError()) + return eResult; + + try + { + if ( pImpl->m_bLocked && bLoading + && GetURLObject().GetProtocol() == INetProtocol::File ) + { + // if the document is already locked the system locking might be temporarily off after storing + // check whether the system file locking should be taken again + GetLockingStream_Impl(); + } + + bool bResult = pImpl->m_bLocked; + + if ( !bResult ) + { + // no read-write access is necessary on loading if the document is explicitly opened as copy + const SfxBoolItem* pTemplateItem = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_TEMPLATE, false); + bResult = ( bLoading && pTemplateItem && pTemplateItem->GetValue() ); + } + + if ( !bResult && !IsReadOnly() ) + { + bool bContentReadonly = false; + if ( bLoading && GetURLObject().GetProtocol() == INetProtocol::File ) + { + // let the original document be opened to check the possibility to open it for editing + // and to let the writable stream stay open to hold the lock on the document + GetLockingStream_Impl(); + } + + // "IsReadOnly" property does not allow to detect whether the file is readonly always + // so we try always to open the file for editing + // the file is readonly only in case the read-write stream can not be opened + if ( bLoading && !pImpl->m_xLockingStream.is() ) + { + try + { + // MediaDescriptor does this check also, the duplication should be avoided in future + Reference< css::ucb::XCommandEnvironment > xDummyEnv; + ::ucbhelper::Content aContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext() ); + aContent.getPropertyValue("IsReadOnly") >>= bContentReadonly; + } + catch( const uno::Exception& ) {} + } + + // do further checks only if the file not readonly in fs + if ( !bContentReadonly ) + { + // the special file locking should be used only for suitable URLs + if ( isSuitableProtocolForLocking( pImpl->m_aLogicName ) ) + { + + // in case of storing the document should request the output before locking + if ( bLoading ) + { + // let the stream be opened to check the system file locking + GetMedium_Impl(); + if (GetError() != ERRCODE_NONE) { + return eResult; + } + } + + ShowLockResult bUIStatus = ShowLockResult::NoLock; + + // check whether system file locking has been used, the default value is false + bool bUseSystemLock = comphelper::isFileUrl( pImpl->m_aLogicName ) && IsSystemFileLockingUsed(); + + // TODO/LATER: This implementation does not allow to detect the system lock on saving here, actually this is no big problem + // if system lock is used the writeable stream should be available + bool bHandleSysLocked = ( bLoading && bUseSystemLock && !pImpl->xStream.is() && !pImpl->m_pOutStream ); + + // The file is attempted to get locked for the duration of lockfile creation on save + std::unique_ptr<osl::File> pFileLock; + if (!bLoading && bUseSystemLock && pImpl->pTempFile) + { + INetURLObject aDest(GetURLObject()); + OUString aDestURL(aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + + if (comphelper::isFileUrl(aDestURL) || !aDest.removeSegment()) + { + pFileLock = std::make_unique<osl::File>(aDestURL); + auto rc = pFileLock->open(osl_File_OpenFlag_Write); + if (rc == osl::FileBase::E_ACCES) + bHandleSysLocked = true; + } + } + + do + { + try + { + ::svt::DocumentLockFile aLockFile( pImpl->m_aLogicName ); + + std::unique_ptr<svt::MSODocumentLockFile> pMSOLockFile; + const SvtFilterOptions& rOpt = SvtFilterOptions::Get(); + if (rOpt.IsMSOLockFileCreationIsEnabled() && svt::MSODocumentLockFile::IsMSOSupportedFileFormat(pImpl->m_aLogicName)) + { + pMSOLockFile.reset(new svt::MSODocumentLockFile(pImpl->m_aLogicName)); + pImpl->m_bMSOLockFileCreated = true; + } + + bool bIoErr = false; + + if (!bHandleSysLocked) + { + try + { + bResult = aLockFile.CreateOwnLockFile(); + if(pMSOLockFile) + bResult &= pMSOLockFile->CreateOwnLockFile(); + } + catch (const uno::Exception&) + { + if (tools::IsMappedWebDAVPath(GetURLObject().GetMainURL( + INetURLObject::DecodeMechanism::NONE))) + { + // This is a path that redirects to a WebDAV resource; + // so failure creating lockfile is not an error here. + bResult = true; + } + else if (bLoading && !bNoUI) + { + bIoErr = true; + ShowLockFileProblemDialog(MessageDlg::LockFileIgnore); + bResult = true; // always delete the defect lock-file + } + } + + // in case OOo locking is turned off the lock file is still written if possible + // but it is ignored while deciding whether the document should be opened for editing or not + if (!bResult && !IsOOoLockFileUsed() && !bIoErr) + { + bResult = true; + // take the ownership over the lock file + aLockFile.OverwriteOwnLockFile(); + + if(pMSOLockFile) + pMSOLockFile->OverwriteOwnLockFile(); + } + } + + if ( !bResult ) + { + LockFileEntry aData; + try + { + aData = aLockFile.GetLockData(); + } + catch (const io::WrongFormatException&) + { + // we get empty or corrupt data + // info to the user + if (!bIoErr && bLoading && !bNoUI ) + bResult = ShowLockFileProblemDialog(MessageDlg::LockFileCorrupt); + + // not show the Lock Document Dialog + bIoErr = true; + } + catch( const uno::Exception& ) + { + // show the Lock Document Dialog, when locked from other app + bIoErr = !bHandleSysLocked; + } + + bool bOwnLock = false; + + if (!bHandleSysLocked) + { + LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry(); + bOwnLock = aOwnData[LockFileComponent::SYSUSERNAME] == aData[LockFileComponent::SYSUSERNAME]; + + if (bOwnLock + && aOwnData[LockFileComponent::LOCALHOST] == aData[LockFileComponent::LOCALHOST] + && aOwnData[LockFileComponent::USERURL] == aData[LockFileComponent::USERURL]) + { + // this is own lock from the same installation, it could remain because of crash + bResult = true; + } + } + + if ( !bResult && !bIoErr) + { + if (!bNoUI) + bUIStatus = ShowLockedDocumentDialog( + aData, bLoading, bOwnLock, bHandleSysLocked); + else if (bLoading && bTryIgnoreLockFile && !bHandleSysLocked) + bUIStatus = ShowLockResult::Succeeded; + + if ( bUIStatus == ShowLockResult::Succeeded ) + { + // take the ownership over the lock file + bResult = aLockFile.OverwriteOwnLockFile(); + + if(pMSOLockFile) + pMSOLockFile->OverwriteOwnLockFile(); + } + else if (bLoading && !bHandleSysLocked) + eResult = LockFileResult::FailedLockFile; + + if (!bResult && pLockData) + { + std::copy(aData.begin(), aData.end(), pLockData->begin()); + } + } + } + } + catch( const uno::Exception& ) + { + } + } while( !bResult && bUIStatus == ShowLockResult::Try ); + + pImpl->m_bLocked = bResult; + } + else + { + // this is no file URL, check whether the file is readonly + bResult = !bContentReadonly; + } + } + else // read-only + { + AddToCheckEditableWorkerList(); + } + } + + if ( !bResult && GetError() == ERRCODE_NONE ) + { + // the error should be set in case it is storing process + // or the document has been opened for editing explicitly + const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false); + + if ( !bLoading || (pReadOnlyItem && !pReadOnlyItem->GetValue()) ) + SetError(ERRCODE_IO_ACCESSDENIED); + else + GetItemSet()->Put( SfxBoolItem( SID_DOC_READONLY, true ) ); + } + + // when the file is locked, get the current file date + if ( bResult && DocNeedsFileDateCheck() ) + GetInitFileDate( true ); + + if ( bResult ) + eResult = LockFileResult::Succeeded; + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "sfx.doc", "Locking exception: high probability, that the content has not been created" ); + } + + return eResult; +#endif +} + + +uno::Reference < embed::XStorage > SfxMedium::GetStorage( bool bCreateTempFile ) +{ + if ( pImpl->xStorage.is() || pImpl->m_bTriedStorage ) + return pImpl->xStorage; + + uno::Sequence< uno::Any > aArgs( 2 ); + auto pArgs = aArgs.getArray(); + + // the medium should be retrieved before temporary file creation + // to let the MediaDescriptor be filled with the streams + GetMedium_Impl(); + + if ( bCreateTempFile ) + CreateTempFile( false ); + + GetMedium_Impl(); + + if ( GetError() ) + return pImpl->xStorage; + + const SfxBoolItem* pRepairItem = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_REPAIRPACKAGE, false); + if ( pRepairItem && pRepairItem->GetValue() ) + { + // the storage should be created for repairing mode + CreateTempFile( false ); + GetMedium_Impl(); + + Reference< css::ucb::XProgressHandler > xProgressHandler; + Reference< css::task::XStatusIndicator > xStatusIndicator; + + const SfxUnoAnyItem* pxProgressItem = SfxItemSet::GetItem<SfxUnoAnyItem>(GetItemSet(), SID_PROGRESS_STATUSBAR_CONTROL, false); + if( pxProgressItem && ( pxProgressItem->GetValue() >>= xStatusIndicator ) ) + xProgressHandler.set( new utl::ProgressHandlerWrap( xStatusIndicator ) ); + + uno::Sequence< beans::PropertyValue > aAddProps{ + comphelper::makePropertyValue("RepairPackage", true), + comphelper::makePropertyValue("StatusIndicator", xProgressHandler) + }; + + // the first arguments will be filled later + aArgs.realloc( 3 ); + pArgs = aArgs.getArray(); + pArgs[2] <<= aAddProps; + } + + if ( pImpl->xStream.is() ) + { + // since the storage is based on temporary stream we open it always read-write + pArgs[0] <<= pImpl->xStream; + pArgs[1] <<= embed::ElementModes::READWRITE; + pImpl->bStorageBasedOnInStream = true; + if (pImpl->m_bDisableFileSync) + { + // Forward NoFileSync to the storage factory. + aArgs.realloc(3); // ??? this may re-write the data added above for pRepairItem + pArgs = aArgs.getArray(); + uno::Sequence<beans::PropertyValue> aProperties( + comphelper::InitPropertySequence({ { "NoFileSync", uno::Any(true) } })); + pArgs[2] <<= aProperties; + } + } + else if ( pImpl->xInputStream.is() ) + { + // since the storage is based on temporary stream we open it always read-write + pArgs[0] <<= pImpl->xInputStream; + pArgs[1] <<= embed::ElementModes::READ; + pImpl->bStorageBasedOnInStream = true; + } + else + { + CloseStreams_Impl(); + pArgs[0] <<= pImpl->m_aName; + pArgs[1] <<= embed::ElementModes::READ; + pImpl->bStorageBasedOnInStream = false; + } + + try + { + pImpl->xStorage.set( ::comphelper::OStorageHelper::GetStorageFactory()->createInstanceWithArguments( aArgs ), + uno::UNO_QUERY ); + } + catch( const uno::Exception& ) + { + // impossibility to create the storage is no error + } + + if( ( pImpl->nLastStorageError = GetError() ) != ERRCODE_NONE ) + { + pImpl->xStorage = nullptr; + if ( pImpl->m_pInStream ) + pImpl->m_pInStream->Seek(0); + return uno::Reference< embed::XStorage >(); + } + + pImpl->m_bTriedStorage = true; + + // TODO/LATER: Get versionlist on demand + if ( pImpl->xStorage.is() ) + { + SetEncryptionDataToStorage_Impl(); + GetVersionList(); + } + + const SfxInt16Item* pVersion = SfxItemSet::GetItem<SfxInt16Item>(pImpl->m_pSet.get(), SID_VERSION, false); + + bool bResetStorage = false; + if ( pVersion && pVersion->GetValue() ) + { + // Read all available versions + if ( pImpl->aVersions.hasElements() ) + { + // Search for the version fits the comment + // The versions are numbered starting with 1, versions with + // negative versions numbers are counted backwards from the + // current version + short nVersion = pVersion->GetValue(); + if ( nVersion<0 ) + nVersion = static_cast<short>(pImpl->aVersions.getLength()) + nVersion; + else // nVersion > 0; pVersion->GetValue() != 0 was the condition to this block + nVersion--; + + const util::RevisionTag& rTag = pImpl->aVersions[nVersion]; + { + // Open SubStorage for all versions + uno::Reference < embed::XStorage > xSub = pImpl->xStorage->openStorageElement( "Versions", + embed::ElementModes::READ ); + + DBG_ASSERT( xSub.is(), "Version list, but no Versions!" ); + + // There the version is stored as packed Stream + uno::Reference < io::XStream > xStr = xSub->openStreamElement( rTag.Identifier, embed::ElementModes::READ ); + std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream( xStr )); + if ( pStream && pStream->GetError() == ERRCODE_NONE ) + { + // Unpack Stream in TempDir + ::utl::TempFile aTempFile; + const OUString& aTmpName = aTempFile.GetURL(); + SvFileStream aTmpStream( aTmpName, SFX_STREAM_READWRITE ); + + pStream->ReadStream( aTmpStream ); + pStream.reset(); + aTmpStream.Close(); + + // Open data as Storage + pImpl->m_nStorOpenMode = SFX_STREAM_READONLY; + pImpl->xStorage = comphelper::OStorageHelper::GetStorageFromURL( aTmpName, embed::ElementModes::READ ); + pImpl->bStorageBasedOnInStream = false; + OUString aTemp; + osl::FileBase::getSystemPathFromFileURL( aTmpName, aTemp ); + SetPhysicalName_Impl( aTemp ); + + pImpl->bIsTemp = true; + GetItemSet()->Put( SfxBoolItem( SID_DOC_READONLY, true ) ); + // TODO/MBA + pImpl->aVersions.realloc(0); + } + else + bResetStorage = true; + } + } + else + bResetStorage = true; + } + + if ( bResetStorage ) + { + pImpl->xStorage.clear(); + if ( pImpl->m_pInStream ) + pImpl->m_pInStream->Seek( 0 ); + } + + pImpl->bIsStorage = pImpl->xStorage.is(); + return pImpl->xStorage; +} + + +uno::Reference< embed::XStorage > const & SfxMedium::GetZipStorageToSign_Impl( bool bReadOnly ) +{ + if ( !GetError() && !pImpl->m_xZipStorage.is() ) + { + GetMedium_Impl(); + + try + { + // we can not sign document if there is no stream + // should it be possible at all? + if ( !bReadOnly && pImpl->xStream.is() ) + { + pImpl->m_xZipStorage = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream( ZIP_STORAGE_FORMAT_STRING, pImpl->xStream ); + } + else if ( pImpl->xInputStream.is() ) + { + pImpl->m_xZipStorage = ::comphelper::OStorageHelper::GetStorageOfFormatFromInputStream( ZIP_STORAGE_FORMAT_STRING, pImpl->xInputStream ); + } + } + catch( const uno::Exception& ) + { + SAL_WARN( "sfx.doc", "No possibility to get readonly version of storage from medium!" ); + } + + if ( GetError() ) // do not remove warnings + ResetError(); + } + + return pImpl->m_xZipStorage; +} + + +void SfxMedium::CloseZipStorage_Impl() +{ + if ( pImpl->m_xZipStorage.is() ) + { + try { + pImpl->m_xZipStorage->dispose(); + } catch( const uno::Exception& ) + {} + + pImpl->m_xZipStorage.clear(); + } +} + +void SfxMedium::CloseStorage() +{ + if ( pImpl->xStorage.is() ) + { + uno::Reference < lang::XComponent > xComp = pImpl->xStorage; + // in the salvage mode the medium does not own the storage + if ( pImpl->bDisposeStorage && !pImpl->m_bSalvageMode ) + { + try { + xComp->dispose(); + } catch( const uno::Exception& ) + { + SAL_WARN( "sfx.doc", "Medium's storage is already disposed!" ); + } + } + + pImpl->xStorage.clear(); + pImpl->bStorageBasedOnInStream = false; + } + + pImpl->m_bTriedStorage = false; + pImpl->bIsStorage = false; +} + +void SfxMedium::CanDisposeStorage_Impl( bool bDisposeStorage ) +{ + pImpl->bDisposeStorage = bDisposeStorage; +} + +bool SfxMedium::WillDisposeStorageOnClose_Impl() +{ + return pImpl->bDisposeStorage; +} + +StreamMode SfxMedium::GetOpenMode() const +{ + return pImpl->m_nStorOpenMode; +} + +void SfxMedium::SetOpenMode( StreamMode nStorOpen, + bool bDontClose ) +{ + if ( pImpl->m_nStorOpenMode != nStorOpen ) + { + pImpl->m_nStorOpenMode = nStorOpen; + + if( !bDontClose ) + { + if ( pImpl->xStorage.is() ) + CloseStorage(); + + CloseStreams_Impl(); + } + } +} + + +bool SfxMedium::UseBackupToRestore_Impl( ::ucbhelper::Content& aOriginalContent, + const Reference< css::ucb::XCommandEnvironment >& xComEnv ) +{ + try + { + ::ucbhelper::Content aTransactCont( pImpl->m_aBackupURL, xComEnv, comphelper::getProcessComponentContext() ); + + Reference< XInputStream > aOrigInput = aTransactCont.openStream(); + aOriginalContent.writeStream( aOrigInput, true ); + return true; + } + catch( const Exception& ) + { + // in case of failure here the backup file should not be removed + // TODO/LATER: a message should be used to let user know about the backup + pImpl->m_bRemoveBackup = false; + // TODO/LATER: needs a specific error code + pImpl->m_eError = ERRCODE_IO_GENERAL; + } + + return false; +} + + +bool SfxMedium::StorageCommit_Impl() +{ + bool bResult = false; + Reference< css::ucb::XCommandEnvironment > xDummyEnv; + ::ucbhelper::Content aOriginalContent; + + if ( pImpl->xStorage.is() ) + { + if ( !GetError() ) + { + uno::Reference < embed::XTransactedObject > xTrans( pImpl->xStorage, uno::UNO_QUERY ); + if ( xTrans.is() ) + { + try + { + xTrans->commit(); + CloseZipStorage_Impl(); + bResult = true; + } + catch ( const embed::UseBackupException& aBackupExc ) + { + // since the temporary file is created always now, the scenario is close to be impossible + if ( !pImpl->pTempFile ) + { + OSL_ENSURE( !pImpl->m_aBackupURL.isEmpty(), "No backup on storage commit!" ); + if ( !pImpl->m_aBackupURL.isEmpty() + && ::ucbhelper::Content::create( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), + xDummyEnv, comphelper::getProcessComponentContext(), + aOriginalContent ) ) + { + // use backup to restore the file + // the storage has already disconnected from original location + CloseAndReleaseStreams_Impl(); + if ( !UseBackupToRestore_Impl( aOriginalContent, xDummyEnv ) ) + { + // connect the medium to the temporary file of the storage + pImpl->aContent = ::ucbhelper::Content(); + pImpl->m_aName = aBackupExc.TemporaryFileURL; + OSL_ENSURE( !pImpl->m_aName.isEmpty(), "The exception _must_ contain the temporary URL!" ); + } + } + } + + if (!GetError()) + SetError(ERRCODE_IO_GENERAL); + } + catch ( const uno::Exception& ) + { + //TODO/LATER: improve error handling + SetError(ERRCODE_IO_GENERAL); + } + } + } + } + + return bResult; +} + + +void SfxMedium::TransactedTransferForFS_Impl( const INetURLObject& aSource, + const INetURLObject& aDest, + const Reference< css::ucb::XCommandEnvironment >& xComEnv ) +{ + Reference< css::ucb::XCommandEnvironment > xDummyEnv; + ::ucbhelper::Content aOriginalContent; + + try + { + aOriginalContent = ::ucbhelper::Content( aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() ); + } + catch ( const css::ucb::CommandAbortedException& ) + { + pImpl->m_eError = ERRCODE_ABORT; + } + catch ( const css::ucb::CommandFailedException& ) + { + pImpl->m_eError = ERRCODE_ABORT; + } + catch (const css::ucb::ContentCreationException& ex) + { + pImpl->m_eError = ERRCODE_IO_GENERAL; + if ( + (ex.eError == css::ucb::ContentCreationError_NO_CONTENT_PROVIDER ) || + (ex.eError == css::ucb::ContentCreationError_CONTENT_CREATION_FAILED) + ) + { + pImpl->m_eError = ERRCODE_IO_NOTEXISTSPATH; + } + } + catch (const css::uno::Exception&) + { + pImpl->m_eError = ERRCODE_IO_GENERAL; + } + + if( pImpl->m_eError && !pImpl->m_eError.IsWarning() ) + return; + + if ( pImpl->xStorage.is() ) + CloseStorage(); + + CloseStreams_Impl(); + + ::ucbhelper::Content aTempCont; + if( ::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext(), aTempCont ) ) + { + bool bTransactStarted = false; + const SfxBoolItem* pOverWrite = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_OVERWRITE, false); + bool bOverWrite = !pOverWrite || pOverWrite->GetValue(); + bool bResult = false; + + try + { + // tdf#60237 - if the OverWrite property of the MediaDescriptor is set to false, + // try to write the file before trying to rename or copy it + if (!(bOverWrite + && ::utl::UCBContentHelper::IsDocument( + aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE)))) + { + Reference< XInputStream > aTempInput = aTempCont.openStream(); + aOriginalContent.writeStream( aTempInput, bOverWrite ); + bResult = true; + } else { + OUString aSourceMainURL = aSource.GetMainURL(INetURLObject::DecodeMechanism::NONE); + OUString aDestMainURL = aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE); + + sal_uInt64 nAttributes = GetDefaultFileAttributes(aDestMainURL); + if (IsFileMovable(aDest) + && osl::File::replace(aSourceMainURL, aDestMainURL) == osl::FileBase::E_None) + { + if (nAttributes) + // Adjust attributes, source might be created with + // the osl_File_OpenFlag_Private flag. + osl::File::setAttributes(aDestMainURL, nAttributes); + bResult = true; + } + else + { + if( pImpl->m_aBackupURL.isEmpty() ) + DoInternalBackup_Impl( aOriginalContent ); + + if( !pImpl->m_aBackupURL.isEmpty() ) + { + Reference< XInputStream > aTempInput = aTempCont.openStream(); + bTransactStarted = true; + aOriginalContent.setPropertyValue( "Size", uno::Any( sal_Int64(0) ) ); + aOriginalContent.writeStream( aTempInput, bOverWrite ); + bResult = true; + } + else + { + pImpl->m_eError = ERRCODE_SFX_CANTCREATEBACKUP; + } + } + } + } + catch ( const css::ucb::CommandAbortedException& ) + { + pImpl->m_eError = ERRCODE_ABORT; + } + catch ( const css::ucb::CommandFailedException& ) + { + pImpl->m_eError = ERRCODE_ABORT; + } + catch ( const css::ucb::InteractiveIOException& r ) + { + if ( r.Code == IOErrorCode_ACCESS_DENIED ) + pImpl->m_eError = ERRCODE_IO_ACCESSDENIED; + else if ( r.Code == IOErrorCode_NOT_EXISTING ) + pImpl->m_eError = ERRCODE_IO_NOTEXISTS; + else if ( r.Code == IOErrorCode_CANT_READ ) + pImpl->m_eError = ERRCODE_IO_CANTREAD; + else + pImpl->m_eError = ERRCODE_IO_GENERAL; + } + // tdf#60237 - if the file is already present, raise the appropriate error + catch (const css::ucb::NameClashException& ) + { + pImpl->m_eError = ERRCODE_IO_ALREADYEXISTS; + } + catch ( const css::uno::Exception& ) + { + pImpl->m_eError = ERRCODE_IO_GENERAL; + } + + if ( bResult ) + { + if ( pImpl->pTempFile ) + { + pImpl->pTempFile->EnableKillingFile(); + pImpl->pTempFile.reset(); + } + } + else if ( bTransactStarted ) + { + UseBackupToRestore_Impl( aOriginalContent, xDummyEnv ); + } + } + else + pImpl->m_eError = ERRCODE_IO_CANTREAD; +} + + +bool SfxMedium::TryDirectTransfer( const OUString& aURL, SfxItemSet const & aTargetSet ) +{ + if ( GetError() ) + return false; + + // if the document had no password it should be stored without password + // if the document had password it should be stored with the same password + // otherwise the stream copying can not be done + const SfxStringItem* pNewPassItem = aTargetSet.GetItem<SfxStringItem>(SID_PASSWORD, false); + const SfxStringItem* pOldPassItem = SfxItemSet::GetItem<SfxStringItem>(GetItemSet(), SID_PASSWORD, false); + if ( ( !pNewPassItem && !pOldPassItem ) + || ( pNewPassItem && pOldPassItem && pNewPassItem->GetValue() == pOldPassItem->GetValue() ) ) + { + // the filter must be the same + const SfxStringItem* pNewFilterItem = aTargetSet.GetItem<SfxStringItem>(SID_FILTER_NAME, false); + const SfxStringItem* pOldFilterItem = SfxItemSet::GetItem<SfxStringItem>(GetItemSet(), SID_FILTER_NAME, false); + if ( pNewFilterItem && pOldFilterItem && pNewFilterItem->GetValue() == pOldFilterItem->GetValue() ) + { + // get the input stream and copy it + // in case of success return true + uno::Reference< io::XInputStream > xInStream = GetInputStream(); + + ResetError(); + if ( xInStream.is() ) + { + try + { + uno::Reference< io::XSeekable > xSeek( xInStream, uno::UNO_QUERY ); + sal_Int64 nPos = 0; + if ( xSeek.is() ) + { + nPos = xSeek->getPosition(); + xSeek->seek( 0 ); + } + + uno::Reference < css::ucb::XCommandEnvironment > xEnv; + ::ucbhelper::Content aTargetContent( aURL, xEnv, comphelper::getProcessComponentContext() ); + + InsertCommandArgument aInsertArg; + aInsertArg.Data = xInStream; + const SfxBoolItem* pOverWrite = aTargetSet.GetItem<SfxBoolItem>(SID_OVERWRITE, false); + if ( pOverWrite && !pOverWrite->GetValue() ) // argument says: never overwrite + aInsertArg.ReplaceExisting = false; + else + aInsertArg.ReplaceExisting = true; // default is overwrite existing files + + Any aCmdArg; + aCmdArg <<= aInsertArg; + aTargetContent.executeCommand( "insert", + aCmdArg ); + + if ( xSeek.is() ) + xSeek->seek( nPos ); + + return true; + } + catch( const uno::Exception& ) + {} + } + } + } + + return false; +} + + +void SfxMedium::Transfer_Impl() +{ + // The transfer is required only in two cases: either if there is a temporary file or if there is a salvage item + OUString aNameURL; + if ( pImpl->pTempFile ) + aNameURL = pImpl->pTempFile->GetURL(); + else if ( !pImpl->m_aLogicName.isEmpty() && pImpl->m_bSalvageMode ) + { + // makes sense only in case logic name is set + if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aNameURL ) + != osl::FileBase::E_None ) + SAL_WARN( "sfx.doc", "The medium name is not convertible!" ); + } + + if ( aNameURL.isEmpty() || ( pImpl->m_eError && !pImpl->m_eError.IsWarning() ) ) + return; + + SAL_INFO( "sfx.doc", "SfxMedium::Transfer_Impl, copying to target" ); + + Reference < css::ucb::XCommandEnvironment > xEnv; + Reference< XOutputStream > rOutStream; + + // in case an output stream is provided from outside and the URL is correct + // commit to the stream + if (pImpl->m_aLogicName.startsWith("private:stream")) + { + // TODO/LATER: support storing to SID_STREAM + const SfxUnoAnyItem* pOutStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_OUTPUTSTREAM, false); + if( pOutStreamItem && ( pOutStreamItem->GetValue() >>= rOutStream ) ) + { + if ( pImpl->xStorage.is() ) + CloseStorage(); + + CloseStreams_Impl(); + + INetURLObject aSource( aNameURL ); + ::ucbhelper::Content aTempCont; + if( ::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aTempCont ) ) + { + try + { + sal_Int32 nRead; + sal_Int32 nBufferSize = 32767; + Sequence < sal_Int8 > aSequence ( nBufferSize ); + Reference< XInputStream > aTempInput = aTempCont.openStream(); + + do + { + nRead = aTempInput->readBytes ( aSequence, nBufferSize ); + if ( nRead < nBufferSize ) + { + Sequence < sal_Int8 > aTempBuf ( aSequence.getConstArray(), nRead ); + rOutStream->writeBytes ( aTempBuf ); + } + else + rOutStream->writeBytes ( aSequence ); + } + while ( nRead == nBufferSize ); + + // remove temporary file + if ( pImpl->pTempFile ) + { + pImpl->pTempFile->EnableKillingFile(); + pImpl->pTempFile.reset(); + } + } + catch( const Exception& ) + {} + } + } + else + { + SAL_WARN( "sfx.doc", "Illegal Output stream parameter!" ); + SetError(ERRCODE_IO_GENERAL); + } + + // free the reference + if ( pImpl->m_pSet ) + pImpl->m_pSet->ClearItem( SID_OUTPUTSTREAM ); + + return; + } + + GetContent(); + if ( !pImpl->aContent.get().is() ) + { + pImpl->m_eError = ERRCODE_IO_NOTEXISTS; + return; + } + + INetURLObject aDest( GetURLObject() ); + + // source is the temp file written so far + INetURLObject aSource( aNameURL ); + + // a special case, an interaction handler should be used for + // authentication in case it is available + Reference< css::ucb::XCommandEnvironment > xComEnv; + bool bForceInteractionHandler = GetURLObject().isAnyKnownWebDAVScheme(); + Reference< css::task::XInteractionHandler > xInteractionHandler = GetInteractionHandler(bForceInteractionHandler); + if (xInteractionHandler.is()) + xComEnv = new ::ucbhelper::CommandEnvironment( xInteractionHandler, + Reference< css::ucb::XProgressHandler >() ); + + OUString aDestURL( aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + + if ( comphelper::isFileUrl( aDestURL ) || !aDest.removeSegment() ) + { + TransactedTransferForFS_Impl( aSource, aDest, xComEnv ); + + if (!pImpl->m_bDisableFileSync) + { + // Hideous - no clean way to do this, so we re-open the file just to fsync it + osl::File aFile( aDestURL ); + if ( aFile.open( osl_File_OpenFlag_Write ) == osl::FileBase::E_None ) + { + aFile.sync(); + SAL_INFO( "sfx.doc", "fsync'd saved file '" << aDestURL << "'" ); + aFile.close(); + } + } + } + else + { + // create content for the parent folder and call transfer on that content with the source content + // and the destination file name as parameters + ::ucbhelper::Content aSourceContent; + ::ucbhelper::Content aTransferContent; + + ::ucbhelper::Content aDestContent; + (void)::ucbhelper::Content::create( aDestURL, xComEnv, comphelper::getProcessComponentContext(), aDestContent ); + // For checkin, we need the object URL, not the parent folder: + if ( !IsInCheckIn( ) ) + { + // Get the parent URL from the XChild if possible: why would the URL necessarily have + // a hierarchical path? It's not always the case for CMIS. + Reference< css::container::XChild> xChild( aDestContent.get(), uno::UNO_QUERY ); + OUString sParentUrl; + if ( xChild.is( ) ) + { + Reference< css::ucb::XContent > xParent( xChild->getParent( ), uno::UNO_QUERY ); + if ( xParent.is( ) ) + { + sParentUrl = xParent->getIdentifier( )->getContentIdentifier(); + } + } + + if ( sParentUrl.isEmpty() ) + aDestURL = aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + // adjust to above aDest.removeSegment() + else + aDestURL = sParentUrl; + } + + // LongName wasn't defined anywhere, only used here... get the Title instead + // as it's less probably empty + OUString aFileName; + Any aAny = aDestContent.getPropertyValue("Title"); + aAny >>= aFileName; + aAny = aDestContent.getPropertyValue( "ObjectId" ); + OUString sObjectId; + aAny >>= sObjectId; + if ( aFileName.isEmpty() ) + aFileName = GetURLObject().getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ); + + try + { + aTransferContent = ::ucbhelper::Content( aDestURL, xComEnv, comphelper::getProcessComponentContext() ); + } + catch (const css::ucb::ContentCreationException& ex) + { + pImpl->m_eError = ERRCODE_IO_GENERAL; + if ( + (ex.eError == css::ucb::ContentCreationError_NO_CONTENT_PROVIDER ) || + (ex.eError == css::ucb::ContentCreationError_CONTENT_CREATION_FAILED) + ) + { + pImpl->m_eError = ERRCODE_IO_NOTEXISTSPATH; + } + } + catch (const css::uno::Exception&) + { + pImpl->m_eError = ERRCODE_IO_GENERAL; + } + + if ( !pImpl->m_eError || pImpl->m_eError.IsWarning() ) + { + // free resources, otherwise the transfer may fail + if ( pImpl->xStorage.is() ) + CloseStorage(); + + CloseStreams_Impl(); + + (void)::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aSourceContent ); + + // check for external parameters that may customize the handling of NameClash situations + const SfxBoolItem* pOverWrite = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_OVERWRITE, false); + sal_Int32 nNameClash; + if ( pOverWrite && !pOverWrite->GetValue() ) + // argument says: never overwrite + nNameClash = NameClash::ERROR; + else + // default is overwrite existing files + nNameClash = NameClash::OVERWRITE; + + try + { + OUString aMimeType = pImpl->getFilterMimeType(); + ::ucbhelper::InsertOperation eOperation = ::ucbhelper::InsertOperation::Copy; + bool bMajor = false; + OUString sComment; + if ( IsInCheckIn( ) ) + { + eOperation = ::ucbhelper::InsertOperation::Checkin; + const SfxBoolItem* pMajor = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_DOCINFO_MAJOR, false); + bMajor = pMajor && pMajor->GetValue( ); + const SfxStringItem* pComments = SfxItemSet::GetItem<SfxStringItem>(GetItemSet(), SID_DOCINFO_COMMENTS, false); + if ( pComments ) + sComment = pComments->GetValue( ); + } + OUString sResultURL; + aTransferContent.transferContent( + aSourceContent, eOperation, + aFileName, nNameClash, aMimeType, bMajor, sComment, + &sResultURL, sObjectId ); + + if ( !sResultURL.isEmpty( ) ) // Likely to happen only for checkin + SwitchDocumentToFile( sResultURL ); + try + { + if ( GetURLObject().isAnyKnownWebDAVScheme() && + eOperation == ::ucbhelper::InsertOperation::Copy ) + { + // tdf#95272 try to re-issue a lock command when a new file is created. + // This may be needed because some WebDAV servers fail to implement the + // 'LOCK on unallocated reference', see issue comment: + // <https://bugs.documentfoundation.org/show_bug.cgi?id=95792#c8> + // and specification at: + // <http://tools.ietf.org/html/rfc4918#section-7.3> + // If the WebDAV resource is already locked by this LO instance, nothing will + // happen, e.g. the LOCK method will not be sent to the server. + ::ucbhelper::Content aLockContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() ); + aLockContent.lock(); + } + } + catch ( css::uno::Exception & ) + { + TOOLS_WARN_EXCEPTION( "sfx.doc", "LOCK not working while re-issuing it" ); + } + } + catch ( const css::ucb::CommandAbortedException& ) + { + pImpl->m_eError = ERRCODE_ABORT; + } + catch ( const css::ucb::CommandFailedException& ) + { + pImpl->m_eError = ERRCODE_ABORT; + } + catch ( const css::ucb::InteractiveIOException& r ) + { + if ( r.Code == IOErrorCode_ACCESS_DENIED ) + pImpl->m_eError = ERRCODE_IO_ACCESSDENIED; + else if ( r.Code == IOErrorCode_NOT_EXISTING ) + pImpl->m_eError = ERRCODE_IO_NOTEXISTS; + else if ( r.Code == IOErrorCode_CANT_READ ) + pImpl->m_eError = ERRCODE_IO_CANTREAD; + else + pImpl->m_eError = ERRCODE_IO_GENERAL; + } + catch ( const css::uno::Exception& ) + { + pImpl->m_eError = ERRCODE_IO_GENERAL; + } + + // do not switch from temporary file in case of nonfile protocol + } + } + + if ( ( !pImpl->m_eError || pImpl->m_eError.IsWarning() ) && !pImpl->pTempFile ) + { + // without a TempFile the physical and logical name should be the same after successful transfer + if (osl::FileBase::getSystemPathFromFileURL( + GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), pImpl->m_aName ) + != osl::FileBase::E_None) + { + pImpl->m_aName.clear(); + } + pImpl->m_bSalvageMode = false; + } +} + + +void SfxMedium::DoInternalBackup_Impl( const ::ucbhelper::Content& aOriginalContent, + std::u16string_view aPrefix, + const OUString& aExtension, + const OUString& aDestDir ) +{ + if ( !pImpl->m_aBackupURL.isEmpty() ) + return; // the backup was done already + + ::utl::TempFile aTransactTemp( aPrefix, true, &aExtension, &aDestDir ); + + INetURLObject aBackObj( aTransactTemp.GetURL() ); + OUString aBackupName = aBackObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ); + + Reference < css::ucb::XCommandEnvironment > xDummyEnv; + ::ucbhelper::Content aBackupCont; + if( ::ucbhelper::Content::create( aDestDir, xDummyEnv, comphelper::getProcessComponentContext(), aBackupCont ) ) + { + try + { + OUString sMimeType = pImpl->getFilterMimeType(); + aBackupCont.transferContent( aOriginalContent, + ::ucbhelper::InsertOperation::Copy, + aBackupName, + NameClash::OVERWRITE, + sMimeType ); + pImpl->m_aBackupURL = aBackObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + pImpl->m_bRemoveBackup = true; + } + catch( const Exception& ) + {} + } + + if ( pImpl->m_aBackupURL.isEmpty() ) + aTransactTemp.EnableKillingFile(); +} + + +void SfxMedium::DoInternalBackup_Impl( const ::ucbhelper::Content& aOriginalContent ) +{ + if ( !pImpl->m_aBackupURL.isEmpty() ) + return; // the backup was done already + + OUString aFileName = GetURLObject().getName( INetURLObject::LAST_SEGMENT, + true, + INetURLObject::DecodeMechanism::NONE ); + + sal_Int32 nPrefixLen = aFileName.lastIndexOf( '.' ); + OUString aPrefix = ( nPrefixLen == -1 ) ? aFileName : aFileName.copy( 0, nPrefixLen ); + OUString aExtension = ( nPrefixLen == -1 ) ? OUString() : aFileName.copy( nPrefixLen ); + OUString aBakDir = SvtPathOptions().GetBackupPath(); + + // create content for the parent folder ( = backup folder ) + ::ucbhelper::Content aContent; + Reference < css::ucb::XCommandEnvironment > xEnv; + if( ::utl::UCBContentHelper::ensureFolder(comphelper::getProcessComponentContext(), xEnv, aBakDir, aContent) ) + DoInternalBackup_Impl( aOriginalContent, aPrefix, aExtension, aBakDir ); + + if ( !pImpl->m_aBackupURL.isEmpty() ) + return; + + // the copying to the backup catalog failed ( for example because + // of using an encrypted partition as target catalog ) + // since the user did not specify to make backup explicitly + // office should try to make backup in another place, + // target catalog does not look bad for this case ( and looks + // to be the only way for encrypted partitions ) + + INetURLObject aDest = GetURLObject(); + if ( aDest.removeSegment() ) + DoInternalBackup_Impl( aOriginalContent, aPrefix, aExtension, aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); +} + + +void SfxMedium::DoBackup_Impl() +{ + // source file name is the logical name of this medium + INetURLObject aSource( GetURLObject() ); + + // there is nothing to backup in case source file does not exist + if ( !::utl::UCBContentHelper::IsDocument( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) ) + return; + + bool bSuccess = false; + + // get path for backups + OUString aBakDir = SvtPathOptions().GetBackupPath(); + if( !aBakDir.isEmpty() ) + { + // create content for the parent folder ( = backup folder ) + ::ucbhelper::Content aContent; + Reference < css::ucb::XCommandEnvironment > xEnv; + if( ::utl::UCBContentHelper::ensureFolder(comphelper::getProcessComponentContext(), xEnv, aBakDir, aContent) ) + { + // save as ".bak" file + INetURLObject aDest( aBakDir ); + aDest.insertName( aSource.getName() ); + aDest.setExtension( u"bak" ); + OUString aFileName = aDest.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ); + + // create a content for the source file + ::ucbhelper::Content aSourceContent; + if ( ::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aSourceContent ) ) + { + try + { + // do the transfer ( copy source file to backup dir ) + OUString sMimeType = pImpl->getFilterMimeType(); + aContent.transferContent( aSourceContent, + ::ucbhelper::InsertOperation::Copy, + aFileName, + NameClash::OVERWRITE, + sMimeType ); + pImpl->m_aBackupURL = aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + pImpl->m_bRemoveBackup = false; + bSuccess = true; + } + catch ( const css::uno::Exception& ) + { + } + } + } + } + + if ( !bSuccess ) + { + pImpl->m_eError = ERRCODE_SFX_CANTCREATEBACKUP; + } +} + + +void SfxMedium::ClearBackup_Impl() +{ + if( pImpl->m_bRemoveBackup ) + { + // currently a document is always stored in a new medium, + // thus if a backup can not be removed the backup URL should not be cleaned + if ( !pImpl->m_aBackupURL.isEmpty() ) + { + if ( ::utl::UCBContentHelper::Kill( pImpl->m_aBackupURL ) ) + { + pImpl->m_bRemoveBackup = false; + pImpl->m_aBackupURL.clear(); + } + else + { + + SAL_WARN( "sfx.doc", "Couldn't remove backup file!"); + } + } + } + else + pImpl->m_aBackupURL.clear(); +} + + +void SfxMedium::GetLockingStream_Impl() +{ + if ( GetURLObject().GetProtocol() != INetProtocol::File + || pImpl->m_xLockingStream.is() ) + return; + + const SfxUnoAnyItem* pWriteStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_STREAM, false); + if ( pWriteStreamItem ) + pWriteStreamItem->GetValue() >>= pImpl->m_xLockingStream; + + if ( pImpl->m_xLockingStream.is() ) + return; + + // open the original document + uno::Sequence< beans::PropertyValue > xProps; + TransformItems( SID_OPENDOC, *GetItemSet(), xProps ); + utl::MediaDescriptor aMedium( xProps ); + + aMedium.addInputStreamOwnLock(); + + uno::Reference< io::XInputStream > xInputStream; + aMedium[utl::MediaDescriptor::PROP_STREAM] >>= pImpl->m_xLockingStream; + aMedium[utl::MediaDescriptor::PROP_INPUTSTREAM] >>= xInputStream; + + if ( !pImpl->pTempFile && pImpl->m_aName.isEmpty() ) + { + // the medium is still based on the original file, it makes sense to initialize the streams + if ( pImpl->m_xLockingStream.is() ) + pImpl->xStream = pImpl->m_xLockingStream; + + if ( xInputStream.is() ) + pImpl->xInputStream = xInputStream; + + if ( !pImpl->xInputStream.is() && pImpl->xStream.is() ) + pImpl->xInputStream = pImpl->xStream->getInputStream(); + } +} + + +void SfxMedium::GetMedium_Impl() +{ + if ( pImpl->m_pInStream + && (!pImpl->bIsTemp || pImpl->xInputStream.is() || pImpl->m_xInputStreamToLoadFrom.is() || pImpl->xStream.is() || pImpl->m_xLockingStream.is() ) ) + return; + + pImpl->bDownloadDone = false; + Reference< css::task::XInteractionHandler > xInteractionHandler = GetInteractionHandler(); + + //TODO/MBA: need support for SID_STREAM + const SfxUnoAnyItem* pWriteStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_STREAM, false); + const SfxUnoAnyItem* pInStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_INPUTSTREAM, false); + if ( pWriteStreamItem ) + { + pWriteStreamItem->GetValue() >>= pImpl->xStream; + + if ( pInStreamItem ) + pInStreamItem->GetValue() >>= pImpl->xInputStream; + + if ( !pImpl->xInputStream.is() && pImpl->xStream.is() ) + pImpl->xInputStream = pImpl->xStream->getInputStream(); + } + else if ( pInStreamItem ) + { + pInStreamItem->GetValue() >>= pImpl->xInputStream; + } + else + { + uno::Sequence < beans::PropertyValue > xProps; + OUString aFileName; + if (!pImpl->m_aName.isEmpty()) + { + if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aFileName ) + != osl::FileBase::E_None ) + { + SAL_WARN( "sfx.doc", "Physical name not convertible!"); + } + } + else + aFileName = GetName(); + + // in case the temporary file exists the streams should be initialized from it, + // but the original MediaDescriptor should not be changed + bool bFromTempFile = ( pImpl->pTempFile != nullptr ); + + if ( !bFromTempFile ) + { + GetItemSet()->Put( SfxStringItem( SID_FILE_NAME, aFileName ) ); + if( !(pImpl->m_nStorOpenMode & StreamMode::WRITE) ) + GetItemSet()->Put( SfxBoolItem( SID_DOC_READONLY, true ) ); + if (xInteractionHandler.is()) + GetItemSet()->Put( SfxUnoAnyItem( SID_INTERACTIONHANDLER, Any(xInteractionHandler) ) ); + } + + if ( pImpl->m_xInputStreamToLoadFrom.is() ) + { + pImpl->xInputStream = pImpl->m_xInputStreamToLoadFrom; + if (pImpl->m_bInputStreamIsReadOnly) + GetItemSet()->Put( SfxBoolItem( SID_DOC_READONLY, true ) ); + } + else + { + TransformItems( SID_OPENDOC, *GetItemSet(), xProps ); + utl::MediaDescriptor aMedium( xProps ); + + if ( pImpl->m_xLockingStream.is() && !bFromTempFile ) + { + // the medium is not based on the temporary file, so the original stream can be used + pImpl->xStream = pImpl->m_xLockingStream; + } + else + { + if ( bFromTempFile ) + { + aMedium[utl::MediaDescriptor::PROP_URL] <<= aFileName; + aMedium.erase( utl::MediaDescriptor::PROP_READONLY ); + aMedium.addInputStream(); + } + else if ( GetURLObject().GetProtocol() == INetProtocol::File ) + { + // use the special locking approach only for file URLs + aMedium.addInputStreamOwnLock(); + } + else + { + // add a check for protocol, if it's http or https or provide webdav then add + // the interaction handler to be used by the authentication dialog + if ( GetURLObject().isAnyKnownWebDAVScheme() ) + { + aMedium[utl::MediaDescriptor::PROP_AUTHENTICATIONHANDLER] <<= GetInteractionHandler( true ); + } + aMedium.addInputStream(); + } + // the ReadOnly property set in aMedium is ignored + // the check is done in LockOrigFileOnDemand() for file and non-file URLs + + //TODO/MBA: what happens if property is not there?! + aMedium[utl::MediaDescriptor::PROP_STREAM] >>= pImpl->xStream; + aMedium[utl::MediaDescriptor::PROP_INPUTSTREAM] >>= pImpl->xInputStream; + } + + GetContent(); + if ( !pImpl->xInputStream.is() && pImpl->xStream.is() ) + pImpl->xInputStream = pImpl->xStream->getInputStream(); + } + + if ( !bFromTempFile ) + { + //TODO/MBA: need support for SID_STREAM + if ( pImpl->xStream.is() ) + GetItemSet()->Put( SfxUnoAnyItem( SID_STREAM, Any( pImpl->xStream ) ) ); + + GetItemSet()->Put( SfxUnoAnyItem( SID_INPUTSTREAM, Any( pImpl->xInputStream ) ) ); + } + } + + //TODO/MBA: ErrorHandling - how to transport error from MediaDescriptor + if ( !GetError() && !pImpl->xStream.is() && !pImpl->xInputStream.is() ) + SetError(ERRCODE_IO_ACCESSDENIED); + + if ( !GetError() && !pImpl->m_pInStream ) + { + if ( pImpl->xStream.is() ) + pImpl->m_pInStream = utl::UcbStreamHelper::CreateStream( pImpl->xStream ); + else if ( pImpl->xInputStream.is() ) + pImpl->m_pInStream = utl::UcbStreamHelper::CreateStream( pImpl->xInputStream ); + } + + pImpl->bDownloadDone = true; + pImpl->aDoneLink.ClearPendingCall(); + ErrCode nError = GetError(); + pImpl->aDoneLink.Call( reinterpret_cast<void*>(sal_uInt32(nError)) ); +} + +bool SfxMedium::IsRemote() const +{ + return pImpl->m_bRemote; +} + +void SfxMedium::SetUpdatePickList(bool bVal) +{ + pImpl->bUpdatePickList = bVal; +} + +bool SfxMedium::IsUpdatePickList() const +{ + return pImpl->bUpdatePickList; +} + +void SfxMedium::SetLongName(const OUString &rName) +{ + pImpl->m_aLongName = rName; +} + +const OUString& SfxMedium::GetLongName() const +{ + return pImpl->m_aLongName; +} + +void SfxMedium::SetDoneLink( const Link<void*,void>& rLink ) +{ + pImpl->aDoneLink = rLink; +} + +void SfxMedium::Download( const Link<void*,void>& aLink ) +{ + SetDoneLink( aLink ); + GetInStream(); + if ( pImpl->m_pInStream && !aLink.IsSet() ) + { + while( !pImpl->bDownloadDone && !Application::IsQuit()) + Application::Yield(); + } +} + + +void SfxMedium::Init_Impl() +/* [Description] + Includes a valid:: sun:: com:: star:: util:: URL (If a file name was + previously in there) in the logical name and if available sets the + physical name as the file name. + */ + +{ + Reference< XOutputStream > rOutStream; + + // TODO/LATER: handle lifetime of storages + pImpl->bDisposeStorage = false; + + const SfxStringItem* pSalvageItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_DOC_SALVAGE, false); + if ( pSalvageItem && pSalvageItem->GetValue().isEmpty() ) + { + pSalvageItem = nullptr; + pImpl->m_pSet->ClearItem( SID_DOC_SALVAGE ); + } + + if (!pImpl->m_aLogicName.isEmpty()) + { + INetURLObject aUrl( pImpl->m_aLogicName ); + INetProtocol eProt = aUrl.GetProtocol(); + if ( eProt == INetProtocol::NotValid ) + { + SAL_WARN( "sfx.doc", "URL <" << pImpl->m_aLogicName << "> with unknown protocol" ); + } + else + { + if ( aUrl.HasMark() ) + { + std::unique_lock<std::recursive_mutex> chkEditLock; + if (pImpl->m_pCheckEditableWorkerMutex != nullptr) + chkEditLock = std::unique_lock<std::recursive_mutex>( + *(pImpl->m_pCheckEditableWorkerMutex)); + pImpl->m_aLogicName = aUrl.GetURLNoMark( INetURLObject::DecodeMechanism::NONE ); + if (chkEditLock.owns_lock()) + chkEditLock.unlock(); + GetItemSet()->Put( SfxStringItem( SID_JUMPMARK, aUrl.GetMark() ) ); + } + + // try to convert the URL into a physical name - but never change a physical name + // physical name may be set if the logical name is changed after construction + if ( pImpl->m_aName.isEmpty() ) + osl::FileBase::getSystemPathFromFileURL( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), pImpl->m_aName ); + else + { + DBG_ASSERT( pSalvageItem, "Suspicious change of logical name!" ); + } + } + } + + if ( pSalvageItem && !pSalvageItem->GetValue().isEmpty() ) + { + std::unique_lock<std::recursive_mutex> chkEditLock; + if (pImpl->m_pCheckEditableWorkerMutex != nullptr) + chkEditLock + = std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex)); + pImpl->m_aLogicName = pSalvageItem->GetValue(); + pImpl->m_pURLObj.reset(); + if (chkEditLock.owns_lock()) + chkEditLock.unlock(); + pImpl->m_bSalvageMode = true; + } + + // in case output stream is by mistake here + // clear the reference + const SfxUnoAnyItem* pOutStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_OUTPUTSTREAM, false); + if( pOutStreamItem + && ( !( pOutStreamItem->GetValue() >>= rOutStream ) + || !pImpl->m_aLogicName.startsWith("private:stream")) ) + { + pImpl->m_pSet->ClearItem( SID_OUTPUTSTREAM ); + SAL_WARN( "sfx.doc", "Unexpected Output stream parameter!" ); + } + + if (!pImpl->m_aLogicName.isEmpty()) + { + // if the logic name is set it should be set in MediaDescriptor as well + const SfxStringItem* pFileNameItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_FILE_NAME, false); + if ( !pFileNameItem ) + { + // let the ItemSet be created if necessary + GetItemSet()->Put( + SfxStringItem( + SID_FILE_NAME, INetURLObject( pImpl->m_aLogicName ).GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) ); + } + } + + SetIsRemote_Impl(); + + osl::DirectoryItem item; + if (osl::DirectoryItem::get(GetName(), item) == osl::FileBase::E_None) { + osl::FileStatus stat(osl_FileStatus_Mask_Attributes); + if (item.getFileStatus(stat) == osl::FileBase::E_None + && stat.isValid(osl_FileStatus_Mask_Attributes)) + { + if ((stat.getAttributes() & osl_File_Attribute_ReadOnly) != 0) + { + pImpl->m_bOriginallyReadOnly = true; + } + } + } +} + + +SfxMedium::SfxMedium() : pImpl(new SfxMedium_Impl) +{ + Init_Impl(); +} + + +void SfxMedium::UseInteractionHandler( bool bUse ) +{ + pImpl->bAllowDefaultIntHdl = bUse; +} + + +css::uno::Reference< css::task::XInteractionHandler > +SfxMedium::GetInteractionHandler( bool bGetAlways ) +{ + // if interaction isn't allowed explicitly ... return empty reference! + if ( !bGetAlways && !pImpl->bUseInteractionHandler ) + return css::uno::Reference< css::task::XInteractionHandler >(); + + // search a possible existing handler inside cached item set + if ( pImpl->m_pSet ) + { + css::uno::Reference< css::task::XInteractionHandler > xHandler; + const SfxUnoAnyItem* pHandler = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_INTERACTIONHANDLER, false); + if ( pHandler && (pHandler->GetValue() >>= xHandler) && xHandler.is() ) + return xHandler; + } + + // if default interaction isn't allowed explicitly ... return empty reference! + if ( !bGetAlways && !pImpl->bAllowDefaultIntHdl ) + return css::uno::Reference< css::task::XInteractionHandler >(); + + // otherwise return cached default handler ... if it exist. + if ( pImpl->xInteraction.is() ) + return pImpl->xInteraction; + + // create default handler and cache it! + Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + pImpl->xInteraction.set( + task::InteractionHandler::createWithParent(xContext, nullptr), UNO_QUERY_THROW ); + return pImpl->xInteraction; +} + +void SfxMedium::SetFilter( const std::shared_ptr<const SfxFilter>& pFilter ) +{ + pImpl->m_pFilter = pFilter; +} + +const std::shared_ptr<const SfxFilter>& SfxMedium::GetFilter() const +{ + return pImpl->m_pFilter; +} + +sal_uInt32 SfxMedium::CreatePasswordToModifyHash( std::u16string_view aPasswd, bool bWriter ) +{ + sal_uInt32 nHash = 0; + + if ( !aPasswd.empty() ) + { + if ( bWriter ) + { + nHash = ::comphelper::DocPasswordHelper::GetWordHashAsUINT32( aPasswd ); + } + else + { + rtl_TextEncoding nEncoding = osl_getThreadTextEncoding(); + nHash = ::comphelper::DocPasswordHelper::GetXLHashAsUINT16( aPasswd, nEncoding ); + } + } + + return nHash; +} + + +void SfxMedium::Close(bool bInDestruction) +{ + if ( pImpl->xStorage.is() ) + { + CloseStorage(); + } + + CloseStreams_Impl(bInDestruction); + + UnlockFile( false ); +} + +void SfxMedium::CloseAndRelease() +{ + if ( pImpl->xStorage.is() ) + { + CloseStorage(); + } + + CloseAndReleaseStreams_Impl(); + + UnlockFile( true ); +} + +void SfxMedium::DisableUnlockWebDAV( bool bDisableUnlockWebDAV ) +{ + pImpl->m_bDisableUnlockWebDAV = bDisableUnlockWebDAV; +} + +void SfxMedium::DisableFileSync(bool bDisableFileSync) +{ + pImpl->m_bDisableFileSync = bDisableFileSync; +} + +void SfxMedium::UnlockFile( bool bReleaseLockStream ) +{ +#if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT + (void) bReleaseLockStream; +#else + // check if webdav + if ( GetURLObject().isAnyKnownWebDAVScheme() ) + { + // do nothing if WebDAV locking if disabled + // (shouldn't happen because we already skipped locking, + // see LockOrigFileOnDemand, but just in case ...) + if (!IsWebDAVLockingUsed()) + return; + + if ( pImpl->m_bLocked ) + { + // an interaction handler should be used for authentication, if needed + try { + uno::Reference< css::task::XInteractionHandler > xHandler = GetInteractionHandler( true ); + uno::Reference< css::ucb::XCommandEnvironment > xComEnv = new ::ucbhelper::CommandEnvironment( xHandler, + Reference< css::ucb::XProgressHandler >() ); + ucbhelper::Content aContentToUnlock( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext()); + pImpl->m_bLocked = false; + //check if WebDAV unlock was explicitly disabled + if ( !pImpl->m_bDisableUnlockWebDAV ) + aContentToUnlock.unlock(); + } + catch ( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "sfx.doc", "Locking exception: WebDAV while trying to lock the file" ); + } + } + return; + } + + if ( pImpl->m_xLockingStream.is() ) + { + if ( bReleaseLockStream ) + { + try + { + uno::Reference< io::XInputStream > xInStream = pImpl->m_xLockingStream->getInputStream(); + uno::Reference< io::XOutputStream > xOutStream = pImpl->m_xLockingStream->getOutputStream(); + if ( xInStream.is() ) + xInStream->closeInput(); + if ( xOutStream.is() ) + xOutStream->closeOutput(); + } + catch( const uno::Exception& ) + {} + } + + pImpl->m_xLockingStream.clear(); + } + + if ( !pImpl->m_bLocked ) + return; + + ::svt::DocumentLockFile aLockFile( pImpl->m_aLogicName ); + + try + { + pImpl->m_bLocked = false; + // TODO/LATER: A warning could be shown in case the file is not the own one + aLockFile.RemoveFile(); + } + catch( const io::WrongFormatException& ) + { + try + { + // erase the empty or corrupt file + aLockFile.RemoveFileDirectly(); + } + catch( const uno::Exception& ) + {} + } + catch( const uno::Exception& ) + {} + + if(!pImpl->m_bMSOLockFileCreated) + return; + + ::svt::MSODocumentLockFile aMSOLockFile( pImpl->m_aLogicName ); + + try + { + pImpl->m_bLocked = false; + // TODO/LATER: A warning could be shown in case the file is not the own one + aMSOLockFile.RemoveFile(); + } + catch( const io::WrongFormatException& ) + { + try + { + // erase the empty or corrupt file + aMSOLockFile.RemoveFileDirectly(); + } + catch( const uno::Exception& ) + {} + } + catch( const uno::Exception& ) + {} + pImpl->m_bMSOLockFileCreated = false; +#endif +} + +void SfxMedium::CloseAndReleaseStreams_Impl() +{ + CloseZipStorage_Impl(); + + uno::Reference< io::XInputStream > xInToClose = pImpl->xInputStream; + uno::Reference< io::XOutputStream > xOutToClose; + if ( pImpl->xStream.is() ) + { + xOutToClose = pImpl->xStream->getOutputStream(); + + // if the locking stream is closed here the related member should be cleaned + if ( pImpl->xStream == pImpl->m_xLockingStream ) + pImpl->m_xLockingStream.clear(); + } + + // The probably existing SvStream wrappers should be closed first + CloseStreams_Impl(); + + // in case of salvage mode the storage is based on the streams + if ( pImpl->m_bSalvageMode ) + return; + + try + { + if ( xInToClose.is() ) + xInToClose->closeInput(); + if ( xOutToClose.is() ) + xOutToClose->closeOutput(); + } + catch ( const uno::Exception& ) + { + } +} + + +void SfxMedium::CloseStreams_Impl(bool bInDestruction) +{ + CloseInStream_Impl(bInDestruction); + CloseOutStream_Impl(); + + if ( pImpl->m_pSet ) + pImpl->m_pSet->ClearItem( SID_CONTENT ); + + pImpl->aContent = ::ucbhelper::Content(); +} + + +void SfxMedium::SetIsRemote_Impl() +{ + INetURLObject aObj( GetName() ); + switch( aObj.GetProtocol() ) + { + case INetProtocol::Ftp: + case INetProtocol::Http: + case INetProtocol::Https: + pImpl->m_bRemote = true; + break; + default: + pImpl->m_bRemote = GetName().startsWith("private:msgid"); + break; + } + + // As files that are written to the remote transmission must also be able + // to be read. + if (pImpl->m_bRemote) + pImpl->m_nStorOpenMode |= StreamMode::READ; +} + + +void SfxMedium::SetName( const OUString& aNameP, bool bSetOrigURL ) +{ + if (pImpl->aOrigURL.isEmpty()) + pImpl->aOrigURL = pImpl->m_aLogicName; + if( bSetOrigURL ) + pImpl->aOrigURL = aNameP; + std::unique_lock<std::recursive_mutex> chkEditLock; + if (pImpl->m_pCheckEditableWorkerMutex != nullptr) + chkEditLock = std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex)); + pImpl->m_aLogicName = aNameP; + pImpl->m_pURLObj.reset(); + if (chkEditLock.owns_lock()) + chkEditLock.unlock(); + pImpl->aContent = ::ucbhelper::Content(); + Init_Impl(); +} + + +const OUString& SfxMedium::GetOrigURL() const +{ + return pImpl->aOrigURL.isEmpty() ? pImpl->m_aLogicName : pImpl->aOrigURL; +} + + +void SfxMedium::SetPhysicalName_Impl( const OUString& rNameP ) +{ + if ( rNameP != pImpl->m_aName ) + { + pImpl->pTempFile.reset(); + + if ( !pImpl->m_aName.isEmpty() || !rNameP.isEmpty() ) + pImpl->aContent = ::ucbhelper::Content(); + + pImpl->m_aName = rNameP; + pImpl->m_bTriedStorage = false; + pImpl->bIsStorage = false; + } +} + +void SfxMedium::ReOpen() +{ + bool bUseInteractionHandler = pImpl->bUseInteractionHandler; + pImpl->bUseInteractionHandler = false; + GetMedium_Impl(); + pImpl->bUseInteractionHandler = bUseInteractionHandler; +} + +void SfxMedium::CompleteReOpen() +{ + // do not use temporary file for reopen and in case of success throw the temporary file away + bool bUseInteractionHandler = pImpl->bUseInteractionHandler; + pImpl->bUseInteractionHandler = false; + + std::unique_ptr<::utl::TempFile> pTmpFile; + if ( pImpl->pTempFile ) + { + pTmpFile = std::move(pImpl->pTempFile); + pImpl->m_aName.clear(); + } + + GetMedium_Impl(); + + if ( GetError() ) + { + if ( pImpl->pTempFile ) + { + pImpl->pTempFile->EnableKillingFile(); + pImpl->pTempFile.reset(); + } + pImpl->pTempFile = std::move( pTmpFile ); + if ( pImpl->pTempFile ) + pImpl->m_aName = pImpl->pTempFile->GetFileName(); + } + else if (pTmpFile) + { + pTmpFile->EnableKillingFile(); + pTmpFile.reset(); + } + + pImpl->bUseInteractionHandler = bUseInteractionHandler; +} + +SfxMedium::SfxMedium(const OUString &rName, StreamMode nOpenMode, std::shared_ptr<const SfxFilter> pFilter, const std::shared_ptr<SfxItemSet>& pInSet) : + pImpl(new SfxMedium_Impl) +{ + pImpl->m_pSet = pInSet; + pImpl->m_pFilter = std::move(pFilter); + pImpl->m_aLogicName = rName; + pImpl->m_nStorOpenMode = nOpenMode; + Init_Impl(); +} + +SfxMedium::SfxMedium(const OUString &rName, const OUString &rReferer, StreamMode nOpenMode, std::shared_ptr<const SfxFilter> pFilter, const std::shared_ptr<SfxItemSet>& pInSet) : + pImpl(new SfxMedium_Impl) +{ + pImpl->m_pSet = pInSet; + SfxItemSet * s = GetItemSet(); + if (s->GetItem(SID_REFERER) == nullptr) { + s->Put(SfxStringItem(SID_REFERER, rReferer)); + } + pImpl->m_pFilter = std::move(pFilter); + pImpl->m_aLogicName = rName; + pImpl->m_nStorOpenMode = nOpenMode; + Init_Impl(); +} + +SfxMedium::SfxMedium( const uno::Sequence<beans::PropertyValue>& aArgs ) : + pImpl(new SfxMedium_Impl) +{ + SfxAllItemSet *pParams = new SfxAllItemSet( SfxGetpApp()->GetPool() ); + pImpl->m_pSet.reset( pParams ); + TransformParameters( SID_OPENDOC, aArgs, *pParams ); + SetArgs(aArgs); + + OUString aFilterProvider, aFilterName; + { + const SfxStringItem* pItem = nullptr; + if ((pItem = pImpl->m_pSet->GetItemIfSet(SID_FILTER_PROVIDER))) + aFilterProvider = pItem->GetValue(); + + if ((pItem = pImpl->m_pSet->GetItemIfSet(SID_FILTER_NAME))) + aFilterName = pItem->GetValue(); + } + + if (aFilterProvider.isEmpty()) + { + // This is a conventional filter type. + pImpl->m_pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4FilterName( aFilterName ); + } + else + { + // This filter is from an external provider such as orcus. + pImpl->m_pCustomFilter = std::make_shared<SfxFilter>(aFilterProvider, aFilterName); + pImpl->m_pFilter = pImpl->m_pCustomFilter; + } + + const SfxStringItem* pSalvageItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_DOC_SALVAGE, false); + if( pSalvageItem ) + { + // QUESTION: there is some treatment of Salvage in Init_Impl; align! + if ( !pSalvageItem->GetValue().isEmpty() ) + { + // if a URL is provided in SalvageItem that means that the FileName refers to a temporary file + // that must be copied here + + const SfxStringItem* pFileNameItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_FILE_NAME, false); + if (!pFileNameItem) throw uno::RuntimeException(); + OUString aNewTempFileURL = SfxMedium::CreateTempCopyWithExt( pFileNameItem->GetValue() ); + if ( !aNewTempFileURL.isEmpty() ) + { + pImpl->m_pSet->Put( SfxStringItem( SID_FILE_NAME, aNewTempFileURL ) ); + pImpl->m_pSet->ClearItem( SID_INPUTSTREAM ); + pImpl->m_pSet->ClearItem( SID_STREAM ); + pImpl->m_pSet->ClearItem( SID_CONTENT ); + } + else + { + SAL_WARN( "sfx.doc", "Can not create a new temporary file for crash recovery!" ); + } + } + } + + const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false); + if ( pReadOnlyItem && pReadOnlyItem->GetValue() ) + pImpl->m_bOriginallyLoadedReadOnly = true; + + const SfxStringItem* pFileNameItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_FILE_NAME, false); + if (!pFileNameItem) throw uno::RuntimeException(); + pImpl->m_aLogicName = pFileNameItem->GetValue(); + pImpl->m_nStorOpenMode = pImpl->m_bOriginallyLoadedReadOnly + ? SFX_STREAM_READONLY : SFX_STREAM_READWRITE; + Init_Impl(); +} + +void SfxMedium::SetArgs(const uno::Sequence<beans::PropertyValue>& rArgs) +{ + static constexpr OUStringLiteral sStream(u"Stream"); + static constexpr OUStringLiteral sInputStream(u"InputStream"); + comphelper::SequenceAsHashMap aArgsMap(rArgs); + aArgsMap.erase(sStream); + aArgsMap.erase(sInputStream); + pImpl->m_aArgs = aArgsMap.getAsConstPropertyValueList(); +} + +const uno::Sequence<beans::PropertyValue> & SfxMedium::GetArgs() const { return pImpl->m_aArgs; } + +SfxMedium::SfxMedium( const uno::Reference < embed::XStorage >& rStor, const OUString& rBaseURL, const std::shared_ptr<SfxItemSet>& p ) : + pImpl(new SfxMedium_Impl) +{ + OUString aType = SfxFilter::GetTypeFromStorage(rStor); + pImpl->m_pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4EA( aType ); + DBG_ASSERT( pImpl->m_pFilter, "No Filter for storage found!" ); + + Init_Impl(); + pImpl->xStorage = rStor; + pImpl->bDisposeStorage = false; + + // always take BaseURL first, could be overwritten by ItemSet + GetItemSet()->Put( SfxStringItem( SID_DOC_BASEURL, rBaseURL ) ); + if ( p ) + GetItemSet()->Put( *p ); +} + + +SfxMedium::SfxMedium( const uno::Reference < embed::XStorage >& rStor, const OUString& rBaseURL, const OUString &rTypeName, const std::shared_ptr<SfxItemSet>& p ) : + pImpl(new SfxMedium_Impl) +{ + pImpl->m_pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4EA( rTypeName ); + DBG_ASSERT( pImpl->m_pFilter, "No Filter for storage found!" ); + + Init_Impl(); + pImpl->xStorage = rStor; + pImpl->bDisposeStorage = false; + + // always take BaseURL first, could be overwritten by ItemSet + GetItemSet()->Put( SfxStringItem( SID_DOC_BASEURL, rBaseURL ) ); + if ( p ) + GetItemSet()->Put( *p ); +} + +// NOTE: should only be called on main thread +SfxMedium::~SfxMedium() +{ + CancelCheckEditableEntry(); + + // if there is a requirement to clean the backup this is the last possibility to do it + ClearBackup_Impl(); + + Close(/*bInDestruction*/true); + + if( !pImpl->bIsTemp || pImpl->m_aName.isEmpty() ) + return; + + OUString aTemp; + if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aTemp ) + != osl::FileBase::E_None ) + { + SAL_WARN( "sfx.doc", "Physical name not convertible!"); + } + + if ( !::utl::UCBContentHelper::Kill( aTemp ) ) + { + SAL_WARN( "sfx.doc", "Couldn't remove temporary file!"); + } +} + +const OUString& SfxMedium::GetName() const +{ + return pImpl->m_aLogicName; +} + +const INetURLObject& SfxMedium::GetURLObject() const +{ + std::unique_lock<std::recursive_mutex> chkEditLock; + if (pImpl->m_pCheckEditableWorkerMutex != nullptr) + chkEditLock = std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex)); + + if (!pImpl->m_pURLObj) + { + pImpl->m_pURLObj.reset( new INetURLObject( pImpl->m_aLogicName ) ); + pImpl->m_pURLObj->SetMark(u""); + } + + return *pImpl->m_pURLObj; +} + +void SfxMedium::SetExpired_Impl( const DateTime& rDateTime ) +{ + pImpl->aExpireTime = rDateTime; +} + + +bool SfxMedium::IsExpired() const +{ + return pImpl->aExpireTime.IsValidAndGregorian() && pImpl->aExpireTime < DateTime( DateTime::SYSTEM ); +} + + +SfxFrame* SfxMedium::GetLoadTargetFrame() const +{ + return pImpl->wLoadTargetFrame; +} + +void SfxMedium::setStreamToLoadFrom(const css::uno::Reference<css::io::XInputStream>& xInputStream, bool bIsReadOnly ) +{ + pImpl->m_xInputStreamToLoadFrom = xInputStream; + pImpl->m_bInputStreamIsReadOnly = bIsReadOnly; +} + +void SfxMedium::SetLoadTargetFrame(SfxFrame* pFrame ) +{ + pImpl->wLoadTargetFrame = pFrame; +} + + +void SfxMedium::SetStorage_Impl( const uno::Reference < embed::XStorage >& rStor ) +{ + pImpl->xStorage = rStor; +} + + +SfxItemSet* SfxMedium::GetItemSet() const +{ + // this method *must* return an ItemSet, returning NULL can cause crashes + if (!pImpl->m_pSet) + pImpl->m_pSet = std::make_shared<SfxAllItemSet>( SfxGetpApp()->GetPool() ); + return pImpl->m_pSet.get(); +} + + +SvKeyValueIterator* SfxMedium::GetHeaderAttributes_Impl() +{ + if( !pImpl->xAttributes.is() ) + { + pImpl->xAttributes = SvKeyValueIteratorRef( new SvKeyValueIterator ); + + if ( GetContent().is() ) + { + try + { + Any aAny = pImpl->aContent.getPropertyValue("MediaType"); + OUString aContentType; + aAny >>= aContentType; + + pImpl->xAttributes->Append( SvKeyValue( "content-type", aContentType ) ); + } + catch ( const css::uno::Exception& ) + { + } + } + } + + return pImpl->xAttributes.get(); +} + +css::uno::Reference< css::io::XInputStream > const & SfxMedium::GetInputStream() +{ + if ( !pImpl->xInputStream.is() ) + GetMedium_Impl(); + return pImpl->xInputStream; +} + +const uno::Sequence < util::RevisionTag >& SfxMedium::GetVersionList( bool _bNoReload ) +{ + // if the medium has no name, then this medium should represent a new document and can have no version info + if ( ( !_bNoReload || !pImpl->m_bVersionsAlreadyLoaded ) && !pImpl->aVersions.hasElements() && + ( !pImpl->m_aName.isEmpty() || !pImpl->m_aLogicName.isEmpty() ) && GetStorage().is() ) + { + uno::Reference < document::XDocumentRevisionListPersistence > xReader = + document::DocumentRevisionListPersistence::create( comphelper::getProcessComponentContext() ); + try + { + pImpl->aVersions = xReader->load( GetStorage() ); + } + catch ( const uno::Exception& ) + { + } + } + + if ( !pImpl->m_bVersionsAlreadyLoaded ) + pImpl->m_bVersionsAlreadyLoaded = true; + + return pImpl->aVersions; +} + +uno::Sequence < util::RevisionTag > SfxMedium::GetVersionList( const uno::Reference < embed::XStorage >& xStorage ) +{ + uno::Reference < document::XDocumentRevisionListPersistence > xReader = + document::DocumentRevisionListPersistence::create( comphelper::getProcessComponentContext() ); + try + { + return xReader->load( xStorage ); + } + catch ( const uno::Exception& ) + { + } + + return uno::Sequence < util::RevisionTag >(); +} + +void SfxMedium::AddVersion_Impl( util::RevisionTag& rRevision ) +{ + if ( !GetStorage().is() ) + return; + + // To determine a unique name for the stream + std::vector<sal_uInt32> aLongs; + sal_Int32 nLength = pImpl->aVersions.getLength(); + for ( const auto& rVersion : std::as_const(pImpl->aVersions) ) + { + sal_uInt32 nVer = static_cast<sal_uInt32>( o3tl::toInt32(rVersion.Identifier.subView(7))); + size_t n; + for ( n=0; n<aLongs.size(); ++n ) + if ( nVer<aLongs[n] ) + break; + + aLongs.insert( aLongs.begin()+n, nVer ); + } + + std::vector<sal_uInt32>::size_type nKey; + for ( nKey=0; nKey<aLongs.size(); ++nKey ) + if ( aLongs[nKey] > nKey+1 ) + break; + + OUString aRevName = "Version" + OUString::number( nKey + 1 ); + pImpl->aVersions.realloc( nLength+1 ); + rRevision.Identifier = aRevName; + pImpl->aVersions.getArray()[nLength] = rRevision; +} + +void SfxMedium::RemoveVersion_Impl( const OUString& rName ) +{ + if ( !pImpl->aVersions.hasElements() ) + return; + + auto pVersion = std::find_if(std::cbegin(pImpl->aVersions), std::cend(pImpl->aVersions), + [&rName](const auto& rVersion) { return rVersion.Identifier == rName; }); + if (pVersion != std::cend(pImpl->aVersions)) + { + auto nIndex = static_cast<sal_Int32>(std::distance(std::cbegin(pImpl->aVersions), pVersion)); + comphelper::removeElementAt(pImpl->aVersions, nIndex); + } +} + +bool SfxMedium::TransferVersionList_Impl( SfxMedium const & rMedium ) +{ + if ( rMedium.pImpl->aVersions.hasElements() ) + { + pImpl->aVersions = rMedium.pImpl->aVersions; + return true; + } + + return false; +} + +void SfxMedium::SaveVersionList_Impl() +{ + if ( !GetStorage().is() ) + return; + + if ( !pImpl->aVersions.hasElements() ) + return; + + uno::Reference < document::XDocumentRevisionListPersistence > xWriter = + document::DocumentRevisionListPersistence::create( comphelper::getProcessComponentContext() ); + try + { + xWriter->store( GetStorage(), pImpl->aVersions ); + } + catch ( const uno::Exception& ) + { + } +} + +bool SfxMedium::IsReadOnly() const +{ + // a) ReadOnly filter can't produce read/write contents! + bool bReadOnly = pImpl->m_pFilter && (pImpl->m_pFilter->GetFilterFlags() & SfxFilterFlags::OPENREADONLY); + + // b) if filter allow read/write contents .. check open mode of the storage + if (!bReadOnly) + bReadOnly = !( GetOpenMode() & StreamMode::WRITE ); + + // c) the API can force the readonly state! + if (!bReadOnly) + { + const SfxBoolItem* pItem = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_DOC_READONLY, false); + if (pItem) + bReadOnly = pItem->GetValue(); + } + + return bReadOnly; +} + +bool SfxMedium::IsOriginallyReadOnly() const +{ + return pImpl->m_bOriginallyReadOnly; +} + +void SfxMedium::SetOriginallyReadOnly(bool val) +{ + pImpl->m_bOriginallyReadOnly = val; +} + +bool SfxMedium::IsOriginallyLoadedReadOnly() const +{ + return pImpl->m_bOriginallyLoadedReadOnly; +} + +bool SfxMedium::SetWritableForUserOnly( const OUString& aURL ) +{ + // UCB does not allow to allow write access only for the user, + // use osl API + bool bResult = false; + + ::osl::DirectoryItem aDirItem; + if ( ::osl::DirectoryItem::get( aURL, aDirItem ) == ::osl::FileBase::E_None ) + { + ::osl::FileStatus aFileStatus( osl_FileStatus_Mask_Attributes ); + if ( aDirItem.getFileStatus( aFileStatus ) == osl::FileBase::E_None + && aFileStatus.isValid( osl_FileStatus_Mask_Attributes ) ) + { + sal_uInt64 nAttributes = aFileStatus.getAttributes(); + + nAttributes &= ~(osl_File_Attribute_OwnWrite | + osl_File_Attribute_GrpWrite | + osl_File_Attribute_OthWrite | + osl_File_Attribute_ReadOnly); + nAttributes |= (osl_File_Attribute_OwnWrite | + osl_File_Attribute_OwnRead); + + bResult = ( osl::File::setAttributes( aURL, nAttributes ) == ::osl::FileBase::E_None ); + } + } + + return bResult; +} + +namespace +{ +/// Get the parent directory of a temporary file for output purposes. +OUString GetLogicBase(const INetURLObject& rURL, std::unique_ptr<SfxMedium_Impl> const & pImpl) +{ + OUString aLogicBase; + +#if HAVE_FEATURE_MACOSX_SANDBOX + // In a sandboxed environment we don't want to attempt to create temporary files in the same + // directory where the user has selected an output file to be stored. The sandboxed process has + // permission only to create the specifically named output file in that directory. + (void) rURL; + (void) pImpl; +#else + + if (!pImpl->m_bHasEmbeddedObjects // Embedded objects would mean a special base, ignore that. + && rURL.GetProtocol() == INetProtocol::File && !pImpl->m_pInStream) + { + // Try to create the temp file in the same directory when storing. + INetURLObject aURL(rURL); + aURL.removeSegment(); + aLogicBase = aURL.GetMainURL(INetURLObject::DecodeMechanism::WithCharset); + } + +#endif // !HAVE_FEATURE_MACOSX_SANDBOX + + return aLogicBase; +} +} + +void SfxMedium::CreateTempFile( bool bReplace ) +{ + if ( pImpl->pTempFile ) + { + if ( !bReplace ) + return; + + pImpl->pTempFile.reset(); + pImpl->m_aName.clear(); + } + + OUString aLogicBase = GetLogicBase(GetURLObject(), pImpl); + pImpl->pTempFile.reset(new ::utl::TempFile(&aLogicBase)); + pImpl->pTempFile->EnableKillingFile(); + pImpl->m_aName = pImpl->pTempFile->GetFileName(); + OUString aTmpURL = pImpl->pTempFile->GetURL(); + if ( pImpl->m_aName.isEmpty() || aTmpURL.isEmpty() ) + { + SetError(ERRCODE_IO_CANTWRITE); + return; + } + + if ( !(pImpl->m_nStorOpenMode & StreamMode::TRUNC) ) + { + bool bTransferSuccess = false; + + if ( GetContent().is() + && GetURLObject().GetProtocol() == INetProtocol::File + && ::utl::UCBContentHelper::IsDocument( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) ) + { + // if there is already such a document, we should copy it + // if it is a file system use OS copy process + try + { + uno::Reference< css::ucb::XCommandEnvironment > xComEnv; + INetURLObject aTmpURLObj( aTmpURL ); + OUString aFileName = aTmpURLObj.getName( INetURLObject::LAST_SEGMENT, + true, + INetURLObject::DecodeMechanism::WithCharset ); + if ( !aFileName.isEmpty() && aTmpURLObj.removeSegment() ) + { + ::ucbhelper::Content aTargetContent( aTmpURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() ); + OUString sMimeType = pImpl->getFilterMimeType(); + aTargetContent.transferContent( pImpl->aContent, ::ucbhelper::InsertOperation::Copy, aFileName, NameClash::OVERWRITE, sMimeType ); + SetWritableForUserOnly( aTmpURL ); + bTransferSuccess = true; + } + } + catch( const uno::Exception& ) + {} + + if ( bTransferSuccess ) + { + CloseOutStream(); + CloseInStream(); + } + } + + if ( !bTransferSuccess && pImpl->m_pInStream ) + { + // the case when there is no URL-access available or this is a remote protocol + // but there is an input stream + GetOutStream(); + if ( pImpl->m_pOutStream ) + { + std::unique_ptr<char[]> pBuf(new char [8192]); + ErrCode nErr = ERRCODE_NONE; + + pImpl->m_pInStream->Seek(0); + pImpl->m_pOutStream->Seek(0); + + while( !pImpl->m_pInStream->eof() && nErr == ERRCODE_NONE ) + { + sal_uInt32 nRead = pImpl->m_pInStream->ReadBytes(pBuf.get(), 8192); + nErr = pImpl->m_pInStream->GetError(); + pImpl->m_pOutStream->WriteBytes( pBuf.get(), nRead ); + } + + bTransferSuccess = true; + CloseInStream(); + } + CloseOutStream_Impl(); + } + else + { + // Quite strange design, but currently it is expected that in this case no transfer happens + // TODO/LATER: get rid of this inconsistent part of the call design + bTransferSuccess = true; + CloseInStream(); + } + + if ( !bTransferSuccess ) + { + SetError(ERRCODE_IO_CANTWRITE); + return; + } + } + + CloseStorage(); +} + + +void SfxMedium::CreateTempFileNoCopy() +{ + // this call always replaces the existing temporary file + pImpl->pTempFile.reset(); + + OUString aLogicBase = GetLogicBase(GetURLObject(), pImpl); + pImpl->pTempFile.reset(new ::utl::TempFile(&aLogicBase)); + pImpl->pTempFile->EnableKillingFile(); + pImpl->m_aName = pImpl->pTempFile->GetFileName(); + if ( pImpl->m_aName.isEmpty() ) + { + SetError(ERRCODE_IO_CANTWRITE); + return; + } + + CloseOutStream_Impl(); + CloseStorage(); +} + +bool SfxMedium::SignDocumentContentUsingCertificate( + const css::uno::Reference<css::frame::XModel>& xModel, bool bHasValidDocumentSignature, + const Reference<XCertificate>& xCertificate) +{ + bool bChanges = false; + + if (IsOpen() || GetError()) + { + SAL_WARN("sfx.doc", "The medium must be closed by the signer!"); + return bChanges; + } + + // The component should know if there was a valid document signature, since + // it should show a warning in this case + OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(GetStorage())); + uno::Reference< security::XDocumentDigitalSignatures > xSigner( + security::DocumentDigitalSignatures::createWithVersionAndValidSignature( + comphelper::getProcessComponentContext(), aODFVersion, bHasValidDocumentSignature ) ); + auto xModelSigner = dynamic_cast<sfx2::DigitalSignatures*>(xSigner.get()); + if (!xModelSigner) + { + return bChanges; + } + + uno::Reference< embed::XStorage > xWriteableZipStor; + + // we can reuse the temporary file if there is one already + CreateTempFile( false ); + GetMedium_Impl(); + + try + { + if ( !pImpl->xStream.is() ) + throw uno::RuntimeException(); + + bool bODF = GetFilter()->IsOwnFormat(); + try + { + xWriteableZipStor = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream( ZIP_STORAGE_FORMAT_STRING, pImpl->xStream ); + } + catch (const io::IOException&) + { + if (bODF) + { + TOOLS_WARN_EXCEPTION("sfx.doc", "ODF stream is not a zip storage"); + } + } + + if ( !xWriteableZipStor.is() && bODF ) + throw uno::RuntimeException(); + + uno::Reference< embed::XStorage > xMetaInf; + if (xWriteableZipStor.is() && xWriteableZipStor->hasByName("META-INF")) + { + xMetaInf = xWriteableZipStor->openStorageElement( + "META-INF", + embed::ElementModes::READWRITE ); + if ( !xMetaInf.is() ) + throw uno::RuntimeException(); + } + + { + if (xMetaInf.is()) + { + // ODF. + uno::Reference< io::XStream > xStream; + if (GetFilter() && GetFilter()->IsOwnFormat()) + xStream.set(xMetaInf->openStreamElement(xSigner->getDocumentContentSignatureDefaultStreamName(), embed::ElementModes::READWRITE), uno::UNO_SET_THROW); + + bool bSuccess = xModelSigner->SignModelWithCertificate( + xModel, xCertificate, GetZipStorageToSign_Impl(), xStream); + + if (bSuccess) + { + uno::Reference< embed::XTransactedObject > xTransact( xMetaInf, uno::UNO_QUERY_THROW ); + xTransact->commit(); + xTransact.set( xWriteableZipStor, uno::UNO_QUERY_THROW ); + xTransact->commit(); + + // the temporary file has been written, commit it to the original file + Commit(); + bChanges = true; + } + } + else if (xWriteableZipStor.is()) + { + // OOXML. + uno::Reference<io::XStream> xStream; + + // We need read-write to be able to add the signature relation. + bool bSuccess = xModelSigner->SignModelWithCertificate( + xModel, xCertificate, GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream); + + if (bSuccess) + { + uno::Reference<embed::XTransactedObject> xTransact(xWriteableZipStor, uno::UNO_QUERY_THROW); + xTransact->commit(); + + // the temporary file has been written, commit it to the original file + Commit(); + bChanges = true; + } + } + else + { + // Something not ZIP based: e.g. PDF. + std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(GetName(), StreamMode::READ | StreamMode::WRITE)); + uno::Reference<io::XStream> xStream(new utl::OStreamWrapper(*pStream)); + if (xModelSigner->SignModelWithCertificate( + xModel, xCertificate, uno::Reference<embed::XStorage>(), xStream)) + bChanges = true; + } + } + } + catch ( const uno::Exception& ) + { + SAL_WARN( "sfx.doc", "Couldn't use signing functionality!" ); + } + + CloseAndRelease(); + + ResetError(); + + return bChanges; +} + +bool SfxMedium::SignContents_Impl(weld::Window* pDialogParent, + bool bSignScriptingContent, + bool bHasValidDocumentSignature, + const OUString& aSignatureLineId, + const Reference<XCertificate>& xCert, + const Reference<XGraphic>& xValidGraphic, + const Reference<XGraphic>& xInvalidGraphic, + const OUString& aComment) +{ + bool bChanges = false; + + if (IsOpen() || GetError()) + { + SAL_WARN("sfx.doc", "The medium must be closed by the signer!"); + return bChanges; + } + + // The component should know if there was a valid document signature, since + // it should show a warning in this case + OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(GetStorage())); + uno::Reference< security::XDocumentDigitalSignatures > xSigner( + security::DocumentDigitalSignatures::createWithVersionAndValidSignature( + comphelper::getProcessComponentContext(), aODFVersion, bHasValidDocumentSignature ) ); + if (pDialogParent) + xSigner->setParentWindow(pDialogParent->GetXWindow()); + + uno::Reference< embed::XStorage > xWriteableZipStor; + + // we can reuse the temporary file if there is one already + CreateTempFile( false ); + GetMedium_Impl(); + + try + { + if ( !pImpl->xStream.is() ) + throw uno::RuntimeException(); + + bool bODF = GetFilter()->IsOwnFormat(); + try + { + xWriteableZipStor = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream( ZIP_STORAGE_FORMAT_STRING, pImpl->xStream ); + } + catch (const io::IOException&) + { + if (bODF) + { + TOOLS_WARN_EXCEPTION("sfx.doc", "ODF stream is not a zip storage"); + } + } + + if ( !xWriteableZipStor.is() && bODF ) + throw uno::RuntimeException(); + + uno::Reference< embed::XStorage > xMetaInf; + if (xWriteableZipStor.is() && xWriteableZipStor->hasByName("META-INF")) + { + xMetaInf = xWriteableZipStor->openStorageElement( + "META-INF", + embed::ElementModes::READWRITE ); + if ( !xMetaInf.is() ) + throw uno::RuntimeException(); + } + + if ( bSignScriptingContent ) + { + // If the signature has already the document signature it will be removed + // after the scripting signature is inserted. + uno::Reference< io::XStream > xStream( + xMetaInf->openStreamElement( xSigner->getScriptingContentSignatureDefaultStreamName(), + embed::ElementModes::READWRITE ), + uno::UNO_SET_THROW ); + + if ( xSigner->signScriptingContent( GetZipStorageToSign_Impl(), xStream ) ) + { + // remove the document signature if any + OUString aDocSigName = xSigner->getDocumentContentSignatureDefaultStreamName(); + if ( !aDocSigName.isEmpty() && xMetaInf->hasByName( aDocSigName ) ) + xMetaInf->removeElement( aDocSigName ); + + uno::Reference< embed::XTransactedObject > xTransact( xMetaInf, uno::UNO_QUERY_THROW ); + xTransact->commit(); + xTransact.set( xWriteableZipStor, uno::UNO_QUERY_THROW ); + xTransact->commit(); + + // the temporary file has been written, commit it to the original file + Commit(); + bChanges = true; + } + } + else + { + if (xMetaInf.is()) + { + // ODF. + uno::Reference< io::XStream > xStream; + if (GetFilter() && GetFilter()->IsOwnFormat()) + xStream.set(xMetaInf->openStreamElement(xSigner->getDocumentContentSignatureDefaultStreamName(), embed::ElementModes::READWRITE), uno::UNO_SET_THROW); + + bool bSuccess = false; + if (xCert.is()) + bSuccess = xSigner->signSignatureLine( + GetZipStorageToSign_Impl(), xStream, aSignatureLineId, xCert, + xValidGraphic, xInvalidGraphic, aComment); + else + bSuccess = xSigner->signDocumentContent(GetZipStorageToSign_Impl(), + xStream); + + if (bSuccess) + { + uno::Reference< embed::XTransactedObject > xTransact( xMetaInf, uno::UNO_QUERY_THROW ); + xTransact->commit(); + xTransact.set( xWriteableZipStor, uno::UNO_QUERY_THROW ); + xTransact->commit(); + + // the temporary file has been written, commit it to the original file + Commit(); + bChanges = true; + } + } + else if (xWriteableZipStor.is()) + { + // OOXML. + uno::Reference<io::XStream> xStream; + + bool bSuccess = false; + if (xCert.is()) + { + bSuccess = xSigner->signSignatureLine( + GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream, aSignatureLineId, + xCert, xValidGraphic, xInvalidGraphic, aComment); + } + else + { + // We need read-write to be able to add the signature relation. + bSuccess =xSigner->signDocumentContent( + GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream); + } + + if (bSuccess) + { + uno::Reference<embed::XTransactedObject> xTransact(xWriteableZipStor, uno::UNO_QUERY_THROW); + xTransact->commit(); + + // the temporary file has been written, commit it to the original file + Commit(); + bChanges = true; + } + } + else + { + // Something not ZIP based: e.g. PDF. + std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(GetName(), StreamMode::READ | StreamMode::WRITE)); + uno::Reference<io::XStream> xStream(new utl::OStreamWrapper(*pStream)); + if (xSigner->signDocumentContent(uno::Reference<embed::XStorage>(), xStream)) + bChanges = true; + } + } + } + catch ( const uno::Exception& ) + { + SAL_WARN( "sfx.doc", "Couldn't use signing functionality!" ); + } + + CloseAndRelease(); + + ResetError(); + + return bChanges; +} + + +SignatureState SfxMedium::GetCachedSignatureState_Impl() const +{ + return pImpl->m_nSignatureState; +} + + +void SfxMedium::SetCachedSignatureState_Impl( SignatureState nState ) +{ + pImpl->m_nSignatureState = nState; +} + +void SfxMedium::SetHasEmbeddedObjects(bool bHasEmbeddedObjects) +{ + pImpl->m_bHasEmbeddedObjects = bHasEmbeddedObjects; +} + +bool SfxMedium::HasStorage_Impl() const +{ + return pImpl->xStorage.is(); +} + +bool SfxMedium::IsOpen() const +{ + return pImpl->m_pInStream || pImpl->m_pOutStream || pImpl->xStorage.is(); +} + +OUString SfxMedium::CreateTempCopyWithExt( std::u16string_view aURL ) +{ + OUString aResult; + + if ( !aURL.empty() ) + { + size_t nPrefixLen = aURL.rfind( '.' ); + OUString aExt = ( nPrefixLen == std::u16string_view::npos ) ? OUString() : OUString(aURL.substr( nPrefixLen )); + + OUString aNewTempFileURL = ::utl::TempFile( u"", true, &aExt ).GetURL(); + if ( !aNewTempFileURL.isEmpty() ) + { + INetURLObject aSource( aURL ); + INetURLObject aDest( aNewTempFileURL ); + OUString aFileName = aDest.getName( INetURLObject::LAST_SEGMENT, + true, + INetURLObject::DecodeMechanism::WithCharset ); + if ( !aFileName.isEmpty() && aDest.removeSegment() ) + { + try + { + uno::Reference< css::ucb::XCommandEnvironment > xComEnv; + ::ucbhelper::Content aTargetContent( aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() ); + ::ucbhelper::Content aSourceContent( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() ); + aTargetContent.transferContent( aSourceContent, + ::ucbhelper::InsertOperation::Copy, + aFileName, + NameClash::OVERWRITE ); + aResult = aNewTempFileURL; + } + catch( const uno::Exception& ) + {} + } + } + } + + return aResult; +} + +bool SfxMedium::CallApproveHandler(const uno::Reference< task::XInteractionHandler >& xHandler, const uno::Any& rRequest, bool bAllowAbort) +{ + bool bResult = false; + + if ( xHandler.is() ) + { + try + { + uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations( bAllowAbort ? 2 : 1 ); + auto pContinuations = aContinuations.getArray(); + + ::rtl::Reference< ::comphelper::OInteractionApprove > pApprove( new ::comphelper::OInteractionApprove ); + pContinuations[ 0 ] = pApprove.get(); + + if ( bAllowAbort ) + { + ::rtl::Reference< ::comphelper::OInteractionAbort > pAbort( new ::comphelper::OInteractionAbort ); + pContinuations[ 1 ] = pAbort.get(); + } + + xHandler->handle(::framework::InteractionRequest::CreateRequest(rRequest, aContinuations)); + bResult = pApprove->wasSelected(); + } + catch( const Exception& ) + { + } + } + + return bResult; +} + +OUString SfxMedium::SwitchDocumentToTempFile() +{ + // the method returns empty string in case of failure + OUString aResult; + OUString aOrigURL = pImpl->m_aLogicName; + + if ( !aOrigURL.isEmpty() ) + { + sal_Int32 nPrefixLen = aOrigURL.lastIndexOf( '.' ); + OUString const aExt = (nPrefixLen == -1) + ? OUString() + : aOrigURL.copy(nPrefixLen); + OUString aNewURL = ::utl::TempFile( u"", true, &aExt ).GetURL(); + + // TODO/LATER: In future the aLogicName should be set to shared folder URL + // and a temporary file should be created. Transport_Impl should be impossible then. + if ( !aNewURL.isEmpty() ) + { + uno::Reference< embed::XStorage > xStorage = GetStorage(); + uno::Reference< embed::XOptimizedStorage > xOptStorage( xStorage, uno::UNO_QUERY ); + + if ( xOptStorage.is() ) + { + // TODO/LATER: reuse the pImpl->pTempFile if it already exists + CanDisposeStorage_Impl( false ); + Close(); + SetPhysicalName_Impl( OUString() ); + SetName( aNewURL ); + + // remove the readonly state + bool bWasReadonly = false; + pImpl->m_nStorOpenMode = SFX_STREAM_READWRITE; + const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false); + if ( pReadOnlyItem && pReadOnlyItem->GetValue() ) + bWasReadonly = true; + GetItemSet()->ClearItem( SID_DOC_READONLY ); + + GetMedium_Impl(); + LockOrigFileOnDemand( false, false ); + CreateTempFile(); + GetMedium_Impl(); + + if ( pImpl->xStream.is() ) + { + try + { + xOptStorage->writeAndAttachToStream( pImpl->xStream ); + pImpl->xStorage = xStorage; + aResult = aNewURL; + } + catch( const uno::Exception& ) + {} + } + + if (bWasReadonly) + { + // set the readonly state back + pImpl->m_nStorOpenMode = SFX_STREAM_READONLY; + GetItemSet()->Put(SfxBoolItem(SID_DOC_READONLY, true)); + } + + if ( aResult.isEmpty() ) + { + Close(); + SetPhysicalName_Impl( OUString() ); + SetName( aOrigURL ); + GetMedium_Impl(); + pImpl->xStorage = xStorage; + } + } + } + } + + return aResult; +} + +bool SfxMedium::SwitchDocumentToFile( const OUString& aURL ) +{ + // the method is only for storage based documents + bool bResult = false; + OUString aOrigURL = pImpl->m_aLogicName; + + if ( !aURL.isEmpty() && !aOrigURL.isEmpty() ) + { + uno::Reference< embed::XStorage > xStorage = GetStorage(); + uno::Reference< embed::XOptimizedStorage > xOptStorage( xStorage, uno::UNO_QUERY ); + + // TODO/LATER: reuse the pImpl->pTempFile if it already exists + CanDisposeStorage_Impl( false ); + Close(); + SetPhysicalName_Impl( OUString() ); + SetName( aURL ); + + // open the temporary file based document + GetMedium_Impl(); + LockOrigFileOnDemand( false, false ); + CreateTempFile(); + GetMedium_Impl(); + + if ( pImpl->xStream.is() ) + { + try + { + uno::Reference< io::XTruncate > xTruncate( pImpl->xStream, uno::UNO_QUERY_THROW ); + xTruncate->truncate(); + if ( xOptStorage.is() ) + xOptStorage->writeAndAttachToStream( pImpl->xStream ); + pImpl->xStorage = xStorage; + bResult = true; + } + catch( const uno::Exception& ) + {} + } + + if ( !bResult ) + { + Close(); + SetPhysicalName_Impl( OUString() ); + SetName( aOrigURL ); + GetMedium_Impl(); + pImpl->xStorage = xStorage; + } + } + + return bResult; +} + +void SfxMedium::SetInCheckIn( bool bInCheckIn ) +{ + pImpl->m_bInCheckIn = bInCheckIn; +} + +bool SfxMedium::IsInCheckIn( ) const +{ + return pImpl->m_bInCheckIn; +} + +// should only be called on main thread +const std::shared_ptr<std::recursive_mutex>& SfxMedium::GetCheckEditableMutex() const +{ + return pImpl->m_pCheckEditableWorkerMutex; +} + +// should only be called while holding pImpl->m_pCheckEditableWorkerMutex +void SfxMedium::SetWorkerReloadEvent(ImplSVEvent* pEvent) +{ + pImpl->m_pReloadEvent = pEvent; +} + +// should only be called while holding pImpl->m_pCheckEditableWorkerMutex +ImplSVEvent* SfxMedium::GetWorkerReloadEvent() const +{ + return pImpl->m_pReloadEvent; +} + +// should only be called on main thread +void SfxMedium::AddToCheckEditableWorkerList() +{ + if (!pImpl->m_bNotifyWhenEditable) + return; + + CancelCheckEditableEntry(); + + if (pImpl->m_pCheckEditableWorkerMutex == nullptr) + { + pImpl->m_pCheckEditableWorkerMutex = std::make_shared<std::recursive_mutex>(); + if (pImpl->m_pCheckEditableWorkerMutex == nullptr) + return; + } + + pImpl->m_pIsDestructed = std::make_shared<bool>(false); + if (pImpl->m_pIsDestructed == nullptr) + return; + + std::unique_lock<std::mutex> globalLock(g_chkReadOnlyGlobalMutex); + if (g_newReadOnlyDocs.find(this) == g_newReadOnlyDocs.end()) + { + bool bAddNewEntry = false; + if (!g_bChkReadOnlyTaskRunning) + { + std::shared_ptr<comphelper::ThreadTaskTag> pTag + = comphelper::ThreadPool::createThreadTaskTag(); + if (pTag != nullptr) + { + g_bChkReadOnlyTaskRunning = true; + bAddNewEntry = true; + comphelper::ThreadPool::getSharedOptimalPool().pushTask( + std::make_unique<CheckReadOnlyTask>(pTag)); + } + } + else + bAddNewEntry = true; + + if (bAddNewEntry) + { + std::shared_ptr<ReadOnlyMediumEntry> newEntry = std::make_shared<ReadOnlyMediumEntry>( + pImpl->m_pCheckEditableWorkerMutex, pImpl->m_pIsDestructed); + + if (newEntry != nullptr) + { + g_newReadOnlyDocs[this] = newEntry; + } + } + } +} + +// should only be called on main thread +void SfxMedium::CancelCheckEditableEntry(bool bRemoveEvent) +{ + if (pImpl->m_pCheckEditableWorkerMutex != nullptr) + { + std::unique_lock<std::recursive_mutex> lock(*(pImpl->m_pCheckEditableWorkerMutex)); + + if (pImpl->m_pReloadEvent != nullptr) + { + if (bRemoveEvent) + Application::RemoveUserEvent(pImpl->m_pReloadEvent); + // make sure destructor doesn't use a freed reference + // and reset the event so we can check again + pImpl->m_pReloadEvent = nullptr; + } + + if (pImpl->m_pIsDestructed != nullptr) + { + *(pImpl->m_pIsDestructed) = true; + pImpl->m_pIsDestructed = nullptr; + } + } +} + +/** callback function, which is triggered by worker thread after successfully checking if the file + is editable. Sent from <Application::PostUserEvent(..)> + Note: This method has to be run in the main thread. +*/ +IMPL_STATIC_LINK(SfxMedium, ShowReloadEditableDialog, void*, p, void) +{ + SfxMedium* pMed = static_cast<SfxMedium*>(p); + if (pMed == nullptr) + return; + + pMed->CancelCheckEditableEntry(false); + + uno::Reference<task::XInteractionHandler> xHandler = pMed->GetInteractionHandler(); + if (xHandler.is()) + { + OUString aDocumentURL + = pMed->GetURLObject().GetLastName(INetURLObject::DecodeMechanism::WithCharset); + ::rtl::Reference<::ucbhelper::InteractionRequest> xInteractionRequestImpl + = new ::ucbhelper::InteractionRequest(uno::Any(document::ReloadEditableRequest( + OUString(), uno::Reference<uno::XInterface>(), aDocumentURL))); + if (xInteractionRequestImpl != nullptr) + { + uno::Sequence<uno::Reference<task::XInteractionContinuation>> aContinuations{ + new ::ucbhelper::InteractionAbort(xInteractionRequestImpl.get()), + new ::ucbhelper::InteractionApprove(xInteractionRequestImpl.get()) + }; + xInteractionRequestImpl->setContinuations(aContinuations); + xHandler->handle(xInteractionRequestImpl); + ::rtl::Reference<::ucbhelper::InteractionContinuation> xSelected + = xInteractionRequestImpl->getSelection(); + if (uno::Reference<task::XInteractionApprove>(xSelected.get(), uno::UNO_QUERY).is()) + { + for (SfxViewFrame* pFrame = SfxViewFrame::GetFirst(); pFrame; + pFrame = SfxViewFrame::GetNext(*pFrame)) + { + if (pFrame->GetObjectShell()->GetMedium() == pMed) + { + // special case to ensure view isn't set to read-only in + // SfxViewFrame::ExecReload_Impl after reloading + pMed->SetOriginallyReadOnly(false); + pFrame->GetDispatcher()->Execute(SID_RELOAD); + break; + } + } + } + } + } +} + +bool SfxMedium::CheckCanGetLockfile() const +{ +#if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT + bool bCanReload = true; +#else + bool bCanReload = false; + ::svt::DocumentLockFile aLockFile(GetName()); + LockFileEntry aData; + osl::DirectoryItem rItem; + auto nError1 = osl::DirectoryItem::get(aLockFile.GetURL(), rItem); + if (nError1 == osl::FileBase::E_None) + { + try + { + aData = aLockFile.GetLockData(); + } + catch (const io::WrongFormatException&) + { + // we get empty or corrupt data + return false; + } + catch (const uno::Exception&) + { + // locked from other app + return false; + } + LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry(); + bool bOwnLock + = aOwnData[LockFileComponent::SYSUSERNAME] == aData[LockFileComponent::SYSUSERNAME]; + if (bOwnLock + && aOwnData[LockFileComponent::LOCALHOST] == aData[LockFileComponent::LOCALHOST] + && aOwnData[LockFileComponent::USERURL] == aData[LockFileComponent::USERURL]) + { + // this is own lock from the same installation, it could remain because of crash + bCanReload = true; + } + } + else if (nError1 == osl::FileBase::E_NOENT) // file doesn't exist + { + try + { + aLockFile.CreateOwnLockFile(); + try + { + // TODO/LATER: A warning could be shown in case the file is not the own one + aLockFile.RemoveFile(); + } + catch (const io::WrongFormatException&) + { + try + { + // erase the empty or corrupt file + aLockFile.RemoveFileDirectly(); + } + catch (const uno::Exception&) + { + } + } + bCanReload = true; + } + catch (const uno::Exception&) + { + } + } +#endif + return bCanReload; +} + +// worker thread method, should only be one thread globally +void CheckReadOnlyTask::doWork() +{ + if (m_xListener == nullptr) + return; + + while (true) + { + std::unique_lock<std::mutex> termLock(m_xListener->mMutex); + if (m_xListener->mCond.wait_for(termLock, std::chrono::seconds(60), + [this] { return m_xListener->bIsTerminated; })) + // signalled, spurious wakeups should not be possible + return; + + // must have timed-out + termLock.unlock(); + std::unique_lock<std::mutex> globalLock(g_chkReadOnlyGlobalMutex); + for (auto it = g_newReadOnlyDocs.begin(); it != g_newReadOnlyDocs.end(); ) + { + auto [pMed, roEntry] = *it; + g_existingReadOnlyDocs[pMed] = roEntry; + it = g_newReadOnlyDocs.erase(it); + } + if (g_existingReadOnlyDocs.size() == 0) + { + g_bChkReadOnlyTaskRunning = false; + return; + } + globalLock.unlock(); + + auto checkForErase = [](SfxMedium* pMed, const std::shared_ptr<ReadOnlyMediumEntry>& roEntry) -> bool + { + if (pMed == nullptr || roEntry == nullptr || roEntry->_pMutex == nullptr + || roEntry->_pIsDestructed == nullptr) + return true; + + std::unique_lock<std::recursive_mutex> medLock(*(roEntry->_pMutex)); + if (*(roEntry->_pIsDestructed) || pMed->GetWorkerReloadEvent() != nullptr) + return true; + + osl::File aFile( + pMed->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::WithCharset)); + if (aFile.open(osl_File_OpenFlag_Write) != osl::FileBase::E_None) + return false; + + if (!pMed->CheckCanGetLockfile()) + return false; + + if (aFile.close() != osl::FileBase::E_None) + return true; + + // we can load, ask user + ImplSVEvent* pEvent = Application::PostUserEvent( + LINK(nullptr, SfxMedium, ShowReloadEditableDialog), pMed); + pMed->SetWorkerReloadEvent(pEvent); + return true; + }; + + for (auto it = g_existingReadOnlyDocs.begin(); it != g_existingReadOnlyDocs.end(); ) + { + if (checkForErase(it->first, it->second)) + it = g_existingReadOnlyDocs.erase(it); + else + ++it; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |