5097 lines
182 KiB
C++
5097 lines
182 KiB
C++
/* -*- 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_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;
|
|
|
|
// 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 >( 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 >( 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;
|
|
}
|
|
}
|
|
|
|
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
|
|
Reference<uno::XComponentContext> const& xContext(::comphelper::getProcessComponentContext());
|
|
uno::Reference<io::XStream> const xDecryptedInnerPackageStream(
|
|
xContext->getServiceManager()->createInstanceWithContext(
|
|
u"com.sun.star.comp.MemoryStream"_ustr, xContext),
|
|
UNO_QUERY_THROW);
|
|
comphelper::OStorageHelper::CopyInputToOutput(xDecryptedInnerPackage->getInputStream(), xDecryptedInnerPackageStream->getOutputStream());
|
|
xDecryptedInnerPackageStream->getOutputStream()->closeOutput();
|
|
#if 0
|
|
// debug: dump to temp file
|
|
uno::Reference<io::XTempFile> const xTempFile(io::TempFile::create(xContext), uno::UNO_SET_THROW);
|
|
xTempFile->setRemoveFile(false);
|
|
comphelper::OStorageHelper::CopyInputToOutput(xDecryptedInnerPackageStream->getInputStream(), xTempFile->getOutputStream());
|
|
xTempFile->getOutputStream()->closeOutput();
|
|
SAL_DE BUG("AAA tempfile " << xTempFile->getResourceName());
|
|
uno::Reference<io::XSeekable>(xDecryptedInnerPackageStream, uno::UNO_QUERY_THROW)->seek(0);
|
|
#endif
|
|
// create inner storage; opening the stream should have already verified
|
|
// the password so any failure here is probably due to a bug
|
|
xRet = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
|
|
PACKAGE_STORAGE_FORMAT_STRING, xDecryptedInnerPackageStream,
|
|
embed::ElementModes::READWRITE, xContext, false);
|
|
assert(xRet.is());
|
|
// consistency check: outer and inner package must have same mimetype
|
|
OUString const outerMediaType(uno::Reference<beans::XPropertySet>(pImpl->xStorage,
|
|
uno::UNO_QUERY_THROW)->getPropertyValue(u"MediaType"_ustr).get<OUString>());
|
|
OUString const innerMediaType(uno::Reference<beans::XPropertySet>(xRet,
|
|
uno::UNO_QUERY_THROW)->getPropertyValue(u"MediaType"_ustr).get<OUString>());
|
|
if (outerMediaType.isEmpty() || outerMediaType != innerMediaType)
|
|
{
|
|
throw io::WrongFormatException(u"MediaType inconsistent in encrypted ODF package"_ustr);
|
|
}
|
|
// success:
|
|
pImpl->m_bODFWholesomeEncryption = true;
|
|
pImpl->m_xODFDecryptedInnerPackageStream = xDecryptedInnerPackageStream;
|
|
pImpl->m_xODFEncryptedOuterStorage = xStorage;
|
|
pImpl->xStorage = xRet;
|
|
}
|
|
return xRet;
|
|
}
|
|
|
|
bool SfxMedium::IsRepairPackage() const
|
|
{
|
|
const SfxBoolItem* pRepairItem = GetItemSet().GetItem(SID_REPAIRPACKAGE, false);
|
|
return pRepairItem && pRepairItem->GetValue();
|
|
}
|
|
|
|
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 ( GetErrorIgnoreWarning() )
|
|
return pImpl->xStorage;
|
|
|
|
if (IsRepairPackage())
|
|
{
|
|
// 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 = GetItemSet().GetItem(SID_PROGRESS_STATUSBAR_CONTROL, false);
|
|
if( pxProgressItem && ( pxProgressItem->GetValue() >>= xStatusIndicator ) )
|
|
xProgressHandler.set( new utl::ProgressHandlerWrap( xStatusIndicator ) );
|
|
|
|
uno::Sequence< beans::PropertyValue > aAddProps{
|
|
comphelper::makePropertyValue(u"RepairPackage"_ustr, true),
|
|
comphelper::makePropertyValue(u"StatusIndicator"_ustr, 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
|
|
}
|
|
|
|
pImpl->nLastStorageError = GetErrorIgnoreWarning();
|
|
if( pImpl->nLastStorageError != ERRCODE_NONE )
|
|
{
|
|
pImpl->xStorage = nullptr;
|
|
if ( pImpl->m_pInStream )
|
|
pImpl->m_pInStream->Seek(0);
|
|
return uno::Reference< embed::XStorage >();
|
|
}
|
|
|
|
pImpl->m_bTriedStorage = true;
|
|
|
|
if (pImpl->xStorage.is())
|
|
{
|
|
pImpl->m_bODFWholesomeEncryption = false;
|
|
if (SetEncryptionDataToStorage_Impl())
|
|
{
|
|
try
|
|
{
|
|
TryEncryptedInnerPackage(pImpl->xStorage);
|
|
}
|
|
catch (Exception const&)
|
|
{
|
|
TOOLS_WARN_EXCEPTION("sfx.doc", "exception from TryEncryptedInnerPackage: ");
|
|
SetError(ERRCODE_IO_GENERAL);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GetErrorCode()) // decryption failed?
|
|
{
|
|
pImpl->xStorage.clear();
|
|
}
|
|
|
|
// TODO/LATER: Get versionlist on demand
|
|
if ( pImpl->xStorage.is() )
|
|
{
|
|
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( u"Versions"_ustr,
|
|
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
|
|
const OUString aTmpName = ::utl::CreateTempURL();
|
|
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();
|
|
pImpl->m_xODFDecryptedInnerPackageStream.clear();
|
|
pImpl->m_xODFEncryptedOuterStorage.clear();
|
|
if ( pImpl->m_pInStream )
|
|
pImpl->m_pInStream->Seek( 0 );
|
|
}
|
|
|
|
pImpl->bIsStorage = pImpl->xStorage.is();
|
|
return pImpl->xStorage;
|
|
}
|
|
|
|
const uno::Reference<embed::XStorage> & SfxMedium::GetScriptingStorageToSign_Impl()
|
|
{
|
|
// this was set when it was initially loaded
|
|
if (pImpl->m_bODFWholesomeEncryption)
|
|
{
|
|
// (partial) scripting signature can only be in inner storage!
|
|
// Note: a "PackageFormat" storage like pImpl->xStorage doesn't work
|
|
// (even if it's not encrypted) because it hides the "META-INF" dir.
|
|
// This "ZipFormat" storage is used only read-only; a writable one is
|
|
// created manually in SignContents_Impl().
|
|
if (!pImpl->m_xODFDecryptedInnerZipStorage.is())
|
|
{
|
|
GetStorage(false);
|
|
// don't care about xStorage here because Zip is readonly
|
|
SAL_WARN_IF(!pImpl->m_xODFDecryptedInnerPackageStream.is(), "sfx.doc", "no inner package stream?");
|
|
if (pImpl->m_xODFDecryptedInnerPackageStream.is())
|
|
{
|
|
pImpl->m_xODFDecryptedInnerZipStorage =
|
|
::comphelper::OStorageHelper::GetStorageOfFormatFromInputStream(
|
|
ZIP_STORAGE_FORMAT_STRING,
|
|
pImpl->m_xODFDecryptedInnerPackageStream->getInputStream(), {},
|
|
IsRepairPackage());
|
|
}
|
|
}
|
|
return pImpl->m_xODFDecryptedInnerZipStorage;
|
|
}
|
|
else
|
|
{
|
|
return GetZipStorageToSign_Impl(true);
|
|
}
|
|
}
|
|
|
|
// note: currently nobody who calls this with "false" writes into an ODF
|
|
// storage that is returned here, that is only for OOXML
|
|
uno::Reference< embed::XStorage > const & SfxMedium::GetZipStorageToSign_Impl( bool bReadOnly )
|
|
{
|
|
if ( !GetErrorIgnoreWarning() && !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, css::embed::ElementModes::READWRITE,
|
|
{}, IsRepairPackage());
|
|
}
|
|
else if ( pImpl->xInputStream.is() )
|
|
{
|
|
pImpl->m_xZipStorage
|
|
= ::comphelper::OStorageHelper::GetStorageOfFormatFromInputStream(
|
|
ZIP_STORAGE_FORMAT_STRING, pImpl->xInputStream, {}, IsRepairPackage());
|
|
}
|
|
}
|
|
catch( const uno::Exception& )
|
|
{
|
|
SAL_WARN( "sfx.doc", "No possibility to get readonly version of storage from medium!" );
|
|
}
|
|
|
|
if ( GetErrorIgnoreWarning() ) // 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();
|
|
}
|
|
pImpl->m_xODFDecryptedInnerZipStorage.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->m_xODFDecryptedInnerPackageStream.clear();
|
|
// pImpl->m_xODFDecryptedInnerZipStorage.clear();
|
|
pImpl->m_xODFEncryptedOuterStorage.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 ( !GetErrorIgnoreWarning() )
|
|
{
|
|
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 (!GetErrorIgnoreWarning())
|
|
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 = GetItemSet().GetItem<SfxBoolItem>(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( u"Size"_ustr, 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 && pImpl->m_eError != ERRCODE_ABORT )
|
|
{
|
|
UseBackupToRestore_Impl( aOriginalContent, xDummyEnv );
|
|
}
|
|
}
|
|
else
|
|
pImpl->m_eError = ERRCODE_IO_CANTREAD;
|
|
}
|
|
|
|
|
|
bool SfxMedium::TryDirectTransfer( const OUString& aURL, SfxItemSet const & aTargetSet )
|
|
{
|
|
if ( GetErrorIgnoreWarning() )
|
|
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(SID_PASSWORD, false);
|
|
const SfxStringItem* pOldPassItem = GetItemSet().GetItem(SID_PASSWORD, false);
|
|
if ( ( !pNewPassItem && !pOldPassItem )
|
|
|| ( pNewPassItem && pOldPassItem && pNewPassItem->GetValue() == pOldPassItem->GetValue() ) )
|
|
{
|
|
// the filter must be the same
|
|
const SfxStringItem* pNewFilterItem = aTargetSet.GetItem(SID_FILTER_NAME, false);
|
|
const SfxStringItem* pOldFilterItem = GetItemSet().GetItem(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 = std::move(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( u"insert"_ustr,
|
|
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;
|
|
OUString sObjectId;
|
|
try
|
|
{
|
|
Any aAny = aDestContent.getPropertyValue(u"Title"_ustr);
|
|
aAny >>= aFileName;
|
|
aAny = aDestContent.getPropertyValue(u"ObjectId"_ustr);
|
|
aAny >>= sObjectId;
|
|
}
|
|
catch (uno::Exception const&)
|
|
{
|
|
SAL_INFO("sfx.doc", "exception while getting Title or ObjectId");
|
|
}
|
|
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 = GetItemSet().GetItem<SfxBoolItem>(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 = GetItemSet().GetItem<SfxBoolItem>(SID_DOCINFO_MAJOR, false);
|
|
bMajor = pMajor && pMajor->GetValue( );
|
|
const SfxStringItem* pComments = GetItemSet().GetItem(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,
|
|
std::u16string_view aExtension,
|
|
const OUString& aDestDir )
|
|
{
|
|
if ( !pImpl->m_aBackupURL.isEmpty() )
|
|
return; // the backup was done already
|
|
|
|
::utl::TempFileNamed 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(bool bForceUsingBackupPath)
|
|
{
|
|
// 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;
|
|
bool bOnErrorRetryUsingBackupPath = false;
|
|
|
|
// get path for backups
|
|
OUString aBakDir;
|
|
if (!bForceUsingBackupPath
|
|
&& officecfg::Office::Common::Save::Document::BackupIntoDocumentFolder::get())
|
|
{
|
|
aBakDir = aSource.GetPartBeforeLastName();
|
|
bOnErrorRetryUsingBackupPath = true;
|
|
}
|
|
else
|
|
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() );
|
|
const OUString sExt
|
|
= aSource.hasExtension() ? aSource.getExtension() + ".bak" : u"bak"_ustr;
|
|
aDest.setExtension(sExt);
|
|
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 )
|
|
{
|
|
// in case a webdav server prevents file creation, or a partition is full, or whatever...
|
|
if (bOnErrorRetryUsingBackupPath)
|
|
return DoBackup_Impl(/*bForceUsingBackupPath=*/true);
|
|
|
|
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 = std::move(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 ( !GetErrorIgnoreWarning() && !pImpl->xStream.is() && !pImpl->xInputStream.is() )
|
|
SetError(ERRCODE_IO_ACCESSDENIED);
|
|
|
|
if ( !GetErrorIgnoreWarning() && !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();
|
|
ErrCodeMsg nError = GetErrorIgnoreWarning();
|
|
sal_uIntPtr nErrorCode = sal_uInt32(nError.GetCode());
|
|
pImpl->aDoneLink.Call( reinterpret_cast<void*>(nErrorCode) );
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Sets m_aLogicName to a valid URL and if available sets
|
|
the physical name m_aName to the file name.
|
|
*/
|
|
void SfxMedium::Init_Impl()
|
|
{
|
|
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 )
|
|
{
|
|
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!
|
|
const 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;
|
|
|
|
try
|
|
{
|
|
::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&)
|
|
{
|
|
// erase the empty or corrupt file
|
|
aLockFile.RemoveFileDirectly();
|
|
}
|
|
}
|
|
catch( const uno::Exception& )
|
|
{}
|
|
|
|
if(!pImpl->m_bMSOLockFileCreated)
|
|
return;
|
|
|
|
try
|
|
{
|
|
::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&)
|
|
{
|
|
// erase the empty or corrupt file
|
|
aMSOLockFile.RemoveFileDirectly();
|
|
}
|
|
}
|
|
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<MediumTempFile> pTmpFile;
|
|
if ( pImpl->pTempFile )
|
|
{
|
|
pTmpFile = std::move(pImpl->pTempFile);
|
|
pImpl->m_aName.clear();
|
|
}
|
|
|
|
GetMedium_Impl();
|
|
|
|
if ( GetErrorIgnoreWarning() )
|
|
{
|
|
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>& xStorage)
|
|
{
|
|
pImpl->xStorage = xStorage;
|
|
pImpl->m_bODFWholesomeEncryption = false;
|
|
}
|
|
|
|
void SfxMedium::SetInnerStorage_Impl(const uno::Reference<embed::XStorage>& xStorage)
|
|
{
|
|
pImpl->xStorage = xStorage;
|
|
pImpl->m_bODFWholesomeEncryption = true;
|
|
}
|
|
|
|
SfxItemSet& SfxMedium::GetItemSet() const
|
|
{
|
|
if (!pImpl->m_pSet)
|
|
pImpl->m_pSet = std::make_shared<SfxAllItemSet>( SfxGetpApp()->GetPool() );
|
|
return *pImpl->m_pSet;
|
|
}
|
|
|
|
|
|
SvKeyValueIterator* SfxMedium::GetHeaderAttributes_Impl()
|
|
{
|
|
if( !pImpl->xAttributes.is() )
|
|
{
|
|
pImpl->xAttributes = SvKeyValueIteratorRef( new SvKeyValueIterator );
|
|
|
|
if ( GetContent().is() )
|
|
{
|
|
try
|
|
{
|
|
Any aAny = pImpl->aContent.getPropertyValue(u"MediaType"_ustr);
|
|
OUString aContentType;
|
|
aAny >>= aContentType;
|
|
|
|
pImpl->xAttributes->Append( SvKeyValue( u"content-type"_ustr, 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 : 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;
|
|
|
|
rRevision.Identifier = "Version" + OUString::number( nKey + 1 );
|
|
pImpl->aVersions.realloc( nLength+1 );
|
|
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 = GetItemSet().GetItem(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 (!officecfg::Office::Common::Misc::TempFileNextToLocalFile::get())
|
|
return aLogicBase;
|
|
|
|
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 MediumTempFile(&aLogicBase));
|
|
if (!aLogicBase.isEmpty() && pImpl->pTempFile->GetFileName().isEmpty())
|
|
pImpl->pTempFile.reset(new MediumTempFile(nullptr));
|
|
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 MediumTempFile(&aLogicBase));
|
|
if (!aLogicBase.isEmpty() && pImpl->pTempFile->GetFileName().isEmpty())
|
|
pImpl->pTempFile.reset(new MediumTempFile(nullptr));
|
|
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,
|
|
svl::crypto::SigningContext& rSigningContext)
|
|
{
|
|
bool bChanges = false;
|
|
|
|
if (IsOpen() || GetErrorIgnoreWarning())
|
|
{
|
|
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(u"META-INF"_ustr))
|
|
{
|
|
xMetaInf = xWriteableZipStor->openStorageElement(
|
|
u"META-INF"_ustr,
|
|
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, rSigningContext, 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, rSigningContext, 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, rSigningContext, uno::Reference<embed::XStorage>(), xStream))
|
|
bChanges = true;
|
|
}
|
|
}
|
|
}
|
|
catch ( const uno::Exception& )
|
|
{
|
|
TOOLS_WARN_EXCEPTION("sfx.doc", "Couldn't use signing functionality!");
|
|
}
|
|
|
|
CloseAndRelease();
|
|
|
|
ResetError();
|
|
|
|
return bChanges;
|
|
}
|
|
|
|
// note: this is the only function creating scripting signature
|
|
void SfxMedium::SignContents_Impl(weld::Window* pDialogParent,
|
|
bool bSignScriptingContent,
|
|
bool bHasValidDocumentSignature,
|
|
SfxViewShell* pViewShell,
|
|
const std::function<void(bool)>& rCallback,
|
|
const OUString& aSignatureLineId,
|
|
const Reference<XCertificate>& xCert,
|
|
const Reference<XGraphic>& xValidGraphic,
|
|
const Reference<XGraphic>& xInvalidGraphic,
|
|
const OUString& aComment)
|
|
{
|
|
bool bChanges = false;
|
|
|
|
if (IsOpen() || GetErrorIgnoreWarning())
|
|
{
|
|
SAL_WARN("sfx.doc", "The medium must be closed by the signer!");
|
|
rCallback(bChanges);
|
|
return;
|
|
}
|
|
|
|
// 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();
|
|
|
|
auto onSignDocumentContentFinished = [this, rCallback](bool bRet) {
|
|
CloseAndRelease();
|
|
|
|
ResetError();
|
|
|
|
rCallback(bRet);
|
|
};
|
|
|
|
try
|
|
{
|
|
if ( !pImpl->xStream.is() )
|
|
throw uno::RuntimeException();
|
|
|
|
bool bODF = GetFilter()->IsOwnFormat();
|
|
try
|
|
{
|
|
if (pImpl->m_bODFWholesomeEncryption && bSignScriptingContent)
|
|
{
|
|
assert(pImpl->xStorage); // GetStorage was called above
|
|
assert(pImpl->m_xODFDecryptedInnerPackageStream);
|
|
xWriteableZipStor = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
|
|
ZIP_STORAGE_FORMAT_STRING, pImpl->m_xODFDecryptedInnerPackageStream);
|
|
}
|
|
else
|
|
{
|
|
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(u"META-INF"_ustr))
|
|
{
|
|
xMetaInf = xWriteableZipStor->openStorageElement(
|
|
u"META-INF"_ustr,
|
|
embed::ElementModes::READWRITE );
|
|
if ( !xMetaInf.is() )
|
|
throw uno::RuntimeException();
|
|
}
|
|
|
|
auto xModelSigner = dynamic_cast<sfx2::DigitalSignatures*>(xSigner.get());
|
|
assert(xModelSigner);
|
|
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 );
|
|
|
|
// note: the storage passed here must be independent from the
|
|
// xWriteableZipStor because a writable storage can't have 2
|
|
// instances of sub-storage for the same directory open, but with
|
|
// independent storages it somehow works
|
|
xModelSigner->SignScriptingContentAsync(
|
|
GetScriptingStorageToSign_Impl(), xStream,
|
|
[this, xSigner, xMetaInf, xWriteableZipStor,
|
|
onSignDocumentContentFinished](bool bRet) {
|
|
// 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();
|
|
|
|
if (pImpl->m_bODFWholesomeEncryption)
|
|
{ // manually copy the inner package to the outer one
|
|
uno::Reference<io::XSeekable>(pImpl->m_xODFDecryptedInnerPackageStream, uno::UNO_QUERY_THROW)->seek(0);
|
|
uno::Reference<io::XStream> const xEncryptedPackage =
|
|
pImpl->m_xODFEncryptedOuterStorage->openStreamElement(
|
|
u"encrypted-package"_ustr,
|
|
embed::ElementModes::WRITE|embed::ElementModes::TRUNCATE);
|
|
comphelper::OStorageHelper::CopyInputToOutput(pImpl->m_xODFDecryptedInnerPackageStream->getInputStream(), xEncryptedPackage->getOutputStream());
|
|
xTransact.set(pImpl->m_xODFEncryptedOuterStorage, uno::UNO_QUERY_THROW);
|
|
xTransact->commit(); // Commit() below won't do this
|
|
}
|
|
|
|
assert(!pImpl->xStorage.is() // ensure this doesn't overwrite
|
|
|| !uno::Reference<util::XModifiable>(pImpl->xStorage, uno::UNO_QUERY_THROW)->isModified());
|
|
// the temporary file has been written, commit it to the original file
|
|
Commit();
|
|
onSignDocumentContentFinished(bRet);
|
|
});
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Signing the entire document.
|
|
if (xMetaInf.is())
|
|
{
|
|
// ODF.
|
|
uno::Reference< io::XStream > xStream;
|
|
uno::Reference< io::XStream > xScriptingStream;
|
|
if (GetFilter() && GetFilter()->IsOwnFormat())
|
|
{
|
|
bool bImplicitScriptSign = officecfg::Office::Common::Security::Scripting::ImplicitScriptSign::get();
|
|
if (comphelper::LibreOfficeKit::isActive())
|
|
{
|
|
bImplicitScriptSign = true;
|
|
}
|
|
|
|
OUString aDocSigName = xSigner->getDocumentContentSignatureDefaultStreamName();
|
|
bool bHasSignatures = xMetaInf->hasByName(aDocSigName);
|
|
|
|
// C.f. DocumentSignatureHelper::CreateElementList() for the
|
|
// DocumentSignatureMode::Macros case.
|
|
bool bHasMacros = xWriteableZipStor->hasByName(u"Basic"_ustr)
|
|
|| xWriteableZipStor->hasByName(u"Dialogs"_ustr)
|
|
|| xWriteableZipStor->hasByName(u"Scripts"_ustr);
|
|
|
|
xStream.set(xMetaInf->openStreamElement(xSigner->getDocumentContentSignatureDefaultStreamName(), embed::ElementModes::READWRITE), uno::UNO_SET_THROW);
|
|
if (bImplicitScriptSign && bHasMacros && !bHasSignatures)
|
|
{
|
|
xScriptingStream.set(
|
|
xMetaInf->openStreamElement(
|
|
xSigner->getScriptingContentSignatureDefaultStreamName(),
|
|
embed::ElementModes::READWRITE),
|
|
uno::UNO_SET_THROW);
|
|
}
|
|
}
|
|
|
|
bool bSuccess = false;
|
|
auto onODFSignDocumentContentFinished = [this, xMetaInf, xWriteableZipStor]() {
|
|
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();
|
|
};
|
|
if (xCert.is())
|
|
bSuccess = xSigner->signSignatureLine(
|
|
GetZipStorageToSign_Impl(), xStream, aSignatureLineId, xCert,
|
|
xValidGraphic, xInvalidGraphic, aComment);
|
|
else
|
|
{
|
|
if (xScriptingStream.is())
|
|
{
|
|
xModelSigner->SetSignScriptingContent(xScriptingStream);
|
|
}
|
|
|
|
// Async, all code before return has to go into the callback.
|
|
xModelSigner->SignDocumentContentAsync(GetZipStorageToSign_Impl(),
|
|
xStream, pViewShell, [onODFSignDocumentContentFinished, onSignDocumentContentFinished](bool bRet) {
|
|
if (bRet)
|
|
{
|
|
onODFSignDocumentContentFinished();
|
|
}
|
|
|
|
onSignDocumentContentFinished(bRet);
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (bSuccess)
|
|
{
|
|
onODFSignDocumentContentFinished();
|
|
bChanges = true;
|
|
}
|
|
}
|
|
else if (xWriteableZipStor.is())
|
|
{
|
|
// OOXML.
|
|
uno::Reference<io::XStream> xStream;
|
|
|
|
auto onOOXMLSignDocumentContentFinished = [this, xWriteableZipStor]() {
|
|
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();
|
|
};
|
|
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.
|
|
xModelSigner->SignDocumentContentAsync(
|
|
GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream, pViewShell, [onOOXMLSignDocumentContentFinished, onSignDocumentContentFinished](bool bRet) {
|
|
if (bRet)
|
|
{
|
|
onOOXMLSignDocumentContentFinished();
|
|
}
|
|
|
|
onSignDocumentContentFinished(bRet);
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (bSuccess)
|
|
{
|
|
onOOXMLSignDocumentContentFinished();
|
|
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(std::move(pStream)));
|
|
xModelSigner->SignDocumentContentAsync(uno::Reference<embed::XStorage>(), xStream, pViewShell, [onSignDocumentContentFinished](bool bRet) {
|
|
onSignDocumentContentFinished(bRet);
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
catch ( const uno::Exception& )
|
|
{
|
|
TOOLS_WARN_EXCEPTION("sfx.doc", "Couldn't use signing functionality!");
|
|
}
|
|
|
|
onSignDocumentContentFinished(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( '.' );
|
|
std::u16string_view aExt = ( nPrefixLen == std::u16string_view::npos ) ? std::u16string_view() : aURL.substr( nPrefixLen );
|
|
|
|
OUString aNewTempFileURL = ::utl::CreateTempURL( u"", true, aExt );
|
|
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( '.' );
|
|
std::u16string_view aExt = (nPrefixLen == -1)
|
|
? std::u16string_view()
|
|
: aOrigURL.subView(nPrefixLen);
|
|
OUString aNewURL = ::utl::CreateTempURL( u"", true, aExt );
|
|
|
|
// 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 = std::move(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 );
|
|
if (xTruncate)
|
|
{
|
|
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 = std::move(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] = std::move(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(); )
|
|
{
|
|
g_existingReadOnlyDocs[it->first] = it->second;
|
|
it = g_newReadOnlyDocs.erase(it);
|
|
}
|
|
if (g_existingReadOnlyDocs.empty())
|
|
{
|
|
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: */
|