/* -*- 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/beans/XPropertySet.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/InteractiveNetworkReadException.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/io/TempFile.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/util/XModifiable.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/lok.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 <comphelper/diagnose_ex.hxx>
#include <sfx2/digitalsignatures.hxx>
#include <sfx2/viewfrm.hxx>
#include <comphelper/threadpool.hxx>
#include <o3tl/string_view.hxx>
#include <svl/cryptosign.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(std::move(pMutex))
, _pIsDestructed(std::move(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_newReadOn
lyDocs;
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;
// Read-only target path: this would be silently replaced.
if (access(sPath.toUtf8().getStr(), W_OK) == -1)
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();
}
/// Temporary file wrapper to handle tmp file lifecycle
/// for lok fork a background saving worker issues.
class MediumTempFile : public ::utl::TempFileNamed
{
bool m_bWasChild;
public:
MediumTempFile(const OUString *pParent )
: ::utl::TempFileNamed(pParent)
, m_bWasChild(comphelper::LibreOfficeKit::isForkedChild())
{
}
MediumTempFile(const MediumTempFile &rFrom ) = delete;
~MediumTempFile()
{
bool isForked = comphelper::LibreOfficeKit::isForkedChild();
// avoid deletion of files created by the parent
if (isForked && ! m_bWasChild)
{
EnableKillingFile(false);
}
}
};
}
class SfxMedium_Impl
{
public:
StreamMode m_nStorOpenMode;
ErrCodeMsg m_eError;
ErrCodeMsg m_eWarningError;
::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;
/// if true, xStorage is an inner package and not directly from xStream
bool m_bODFWholesomeEncryption = 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<MediumTempFile> 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;
uno::Reference<io::XStream> m_xODFDecryptedInnerPackageStream;
uno::Reference<embed::XStorage> m_xODFEncryptedOuterStorage;
uno::Reference<embed::XStorage> m_xODFDecryptedInnerZipStorage;
ErrCodeMsg 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),
m_eWarningError(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();
}
ErrCodeMsg const & SfxMedium::GetWarningError() const
{
return pImpl->m_eWarningError;
}
ErrCodeMsg const & SfxMedium::GetLastStorageCreationState() const
{
return pImpl->nLastStorageError;
}
void SfxMedium::SetError(const ErrCodeMsg& rError)
{
if (pImpl->m_eError == ERRCODE_NONE || (pImpl->m_eError.IsWarning() && rError.IsError()))
pImpl->m_eError = rError;
}
void SfxMedium::SetWarningError(const ErrCodeMsg& nWarningError)
{
pImpl->m_eWarningError = nWarningError;
}
ErrCodeMsg SfxMedium::GetErrorCode() const
{
ErrCodeMsg 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 >( cppu::getXWeak(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(u"DateModified"_ustr) >>= 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 )
{
if (bForSaving)
{
bool bIsRemote = IsRemote();
if ((bIsRemote && !officecfg::Office::Common::Save::URL::Internet::get())
|| (!bIsRemote && !officecfg::Office::Common::Save::URL::FileSystem::get()))
return OUString();
}
if (const SfxStringItem* pBaseURLItem = GetItemSet().GetItem<SfxStringItem>(SID_DOC_BASEURL))
return pBaseURLItem->GetValue();
OUString aBaseURL;
if (!comphelper::IsFuzzing() && GetContent().is())
{
try
{
Any aAny = pImpl->aContent.getPropertyValue(u"BaseURI"_ustr);
aAny >>= aBaseURL;
}
catch ( const css::uno::Exception& )
{
}
if ( aBaseURL.isEmpty() )
aBaseURL = GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE );
}
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 ( GetErrorIgnoreWarning() )
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 ( GetErrorIgnoreWarning() == ERRCODE_NONE )
{
// does something only in case there is a temporary file ( means aName points to different location than aLogicName )
Transfer_Impl();
}
bool bResult = ( GetErrorIgnoreWarning() == 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 = GetItemSet().GetItem(SID_PREVIEW, false);
if ( pPreview )
bPreview = pPreview->GetValue();
else
{
const SfxStringItem* pFlags = GetItemSet().GetItem(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 ( GetErrorIgnoreWarning() )
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_bODFWholesomeEncryption || 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();
}
bool SfxMedium::SetEncryptionDataToStorage_Impl()
{
// in case media-descriptor contains password it should be used on opening
if ( !pImpl->xStorage.is() || !pImpl->m_pSet )
return false;
uno::Sequence< beans::NamedValue > aEncryptionData;
if ( !GetEncryptionData_Impl( pImpl->m_pSet.get(), aEncryptionData ) )
return false;
// 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" );
SetError(ERRCODE_IO_GENERAL);
return false;
}
return true;
}
#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 >( cppu::getXWeak(xSelected.get()), uno::UNO_QUERY ).is() )
{
SetError(ERRCODE_ABORT);
}
else if ( uno::Reference< task::XInteractionDisapprove >( cppu::getXWeak(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 >(cppu::getXWeak(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 >( cppu::getXWeak(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>(cppu::getXWeak(xSelected.get()), uno::UNO_QUERY).is())
{
SetError(ERRCODE_ABORT);
bReadOnly = false;
}
else if (!uno::Reference<task::XInteractionApprove>(cppu::getXWeak(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;
}
}
namespace
{
// for LOCK request, suppress dialog on 403, typically indicates read-only
// document and there's a 2nd dialog prompting to open a copy anyway
class LockInteractionHandler : public ::cppu::WeakImplHelper<task::XInteractionHandler>
{
private:
uno::Reference<task::XInteractionHandler> m_xHandler;
public:
explicit LockInteractionHandler(uno::Reference<task::XInteractionHandler> const& xHandler)
: m_xHandler(xHandler)
{
}
virtual void SAL_CALL handle(uno::Reference<task::XInteractionRequest> const& xRequest) override
{
ucb::InteractiveNetworkWriteException readException;
ucb::InteractiveNetworkReadException writeException;
if ((xRequest->getRequest() >>= readException)
|| (xRequest->getRequest() >>= writeException))
{
return; // 403 gets reported as one of these; ignore to avoid dialog
}
m_xHandler->handle(xRequest);
}
};
} // namespace
#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 = GetItemSet().GetItem(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 );
// Dialog with error is superfluous:
// on loading, will result in read-only with infobar.
// bNoUI case for Reload failing, will open dialog later.
if (bLoading || bNoUI)
{
xCHandler = new LockInteractionHandler(xCHandler);
}
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(u"DAV:lockdiscovery"_ustr) >>= aLocks) && aLocks.hasElements())
{
// got at least a lock, show the owner of the first lock returned
const 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 && GetErrorIgnoreWarning() == 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 = GetItemSet().GetItem(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(u"IsReadOnly"_ustr) >>= 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 (GetErrorIgnoreWarning() != 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;
if (officecfg::Office::Common::Filter::Microsoft::Import::CreateMSOLockFiles::get() && 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 && GetErrorIgnoreWarning() == 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
}
// this either returns non-null or throws exception
uno::Reference<embed::XStorage>
SfxMedium::TryEncryptedInnerPackage(uno::Reference<embed::XStorage> const & xStorage)
{
uno::Reference<embed::XStorage> xRet;
if (xStorage->hasByName(u"encrypted-package"_ustr))
{
uno::Reference<io::XStream> const
xDecryptedInnerPackage = xStorage->openStreamElement(
u"encrypted-package"_ustr,
embed::ElementModes::READ | embed::ElementModes::NOCREATE);
// either this throws due to wrong password or IO error, or returns stream
assert(xDecryptedInnerPackage.is());
// need a seekable stream => copy
--> --------------------
--> maximum size reached
--> --------------------