/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* 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 <uno/mapping.hxx>
#include <com/sun/star/task/InteractionHandler.hpp>
#include <com/sun/star/uno/Reference.h>
#include <com/sun/star/ucb/XContent.hpp>
#include <com/sun/star/container/XChild.hpp>
#include <com/sun/star/document/XDocumentRevisionListPersistence.hpp>
#include <com/sun/star/document/LockedDocumentRequest.hpp>
#include <com/sun/star/document/LockedOnSavingRequest.hpp>
#include <com/sun/star/document/OwnLockOnDocumentRequest.hpp>
#include <com/sun/star/document/LockFileIgnoreRequest.hpp>
#include <com/sun/star/document/LockFileCorruptRequest.hpp>
#include <com/sun/star/document/ChangedByOthersRequest.hpp>
#include <com/sun/star/beans/XPropertySet.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/graphic/XGraphic.hpp>
#include <com/sun/star/ucb/ContentCreationException.hpp>
#include <com/sun/star/ucb/InteractiveIOException.hpp>
#include <com/sun/star/ucb/UnsupportedDataSinkException.hpp>
#include <com/sun/star/ucb/CommandFailedException.hpp>
#include <com/sun/star/ucb/CommandAbortedException.hpp>
#include <com/sun/star/ucb/InteractiveLockingLockedException.hpp>
#include <com/sun/star/ucb/InteractiveNetworkWriteException.hpp>
#include <com/sun/star/ucb/Lock.hpp>
#include <com/sun/star/ucb/XCommandEnvironment.hpp>
#include <com/sun/star/ucb/XContentIdentifierFactory.hpp>
#include <com/sun/star/ucb/XContentProvider.hpp>
#include <com/sun/star/ucb/XProgressHandler.hpp>
#include <com/sun/star/ucb/XCommandInfo.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/XStreamListener.hpp>
#include <com/sun/star/io/XSeekable.hpp>
#include <com/sun/star/ucb/XSimpleFileAccess.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/ucb/InsertCommandArgument.hpp>
#include <com/sun/star/ucb/NameClash.hpp>
#include <com/sun/star/ucb/TransferInfo.hpp>
#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
#include <com/sun/star/ucb/OpenMode.hpp>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/security/DocumentSignatureInformation.hpp>
#include <com/sun/star/security/DocumentDigitalSignatures.hpp>
#include <com/sun/star/security/XCertificate.hpp>
#include <o3tl/make_unique.hxx>
#include <tools/urlobj.hxx>
#include <tools/fileutil.hxx>
#include <unotools/configmgr.hxx>
#include <unotools/tempfile.hxx>
#include <comphelper/fileurl.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/interaction.hxx>
#include <comphelper/simplefileaccessinteraction.hxx>
#include <framework/interaction.hxx>
#include <unotools/streamhelper.hxx>
#include <unotools/localedatawrapper.hxx>
#include <utility>
#include <svl/stritem.hxx>
#include <svl/eitem.hxx>
#include <svl/lckbitem.hxx>
#include <svtools/sfxecode.hxx>
#include <svl/itemset.hxx>
#include <svl/intitem.hxx>
#include <svtools/svparser.hxx>
#include <cppuhelper/weakref.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/stg.hxx>
#include <sot/storage.hxx>
#include <unotools/saveopt.hxx>
#include <svl/documentlockfile.hxx>
#include <com/sun/star/document/DocumentRevisionListPersistence.hpp>
#include <helper.hxx>
#include <sfx2/request.hxx>
#include <sfx2/app.hxx>
#include <sfx2/frame.hxx>
#include <sfx2/fcontnr.hxx>
#include <sfx2/docfilt.hxx>
#include <sfx2/objsh.hxx>
#include <sfx2/docfac.hxx>
#include <sfx2/sfxsids.hrc>
#include <openflag.hxx>
#include <officecfg/Office/Common.hxx>
#include <comphelper/propertysequence.hxx>
#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 {
#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
/// 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 OUString& rURL)
{
#ifdef MACOSX
(void)rURL;
// Hide extension macOS-specific file property would be lost.
return false;
#else
if (!comphelper::isFileUrl(rURL))
// Not a file:// URL.
return false;
#ifdef UNX
OUString rPath;
if (osl::FileBase::getSystemPathFromFileURL(rURL, rPath) != osl::FileBase::E_None)
return false;
struct stat buf;
if (lstat(rPath.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;
#endif
return true;
#endif
}
} // anonymous namespace
class SfxMedium_Impl
{
public:
StreamMode m_nStorOpenMode;
ErrCode m_eError;
::ucbhelper::Content aContent;
bool bUpdatePickList:1;
bool bIsTemp:1;
bool bDownloadDone:1;
bool bIsStorage:1;
bool bUseInteractionHandler:1;
bool bAllowDefaultIntHdl:1;
bool bDisposeStorage:1;
bool bStorageBasedOnInStream:1;
bool m_bSalvageMode:1;
bool m_bVersionsAlreadyLoaded:1;
bool m_bLocked:1;
bool m_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;
OUString m_aName;
OUString m_aLogicName;
OUString m_aLongName;
mutable std::unique_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::unique_ptr<SvStream> m_pInStream;
std::unique_ptr<SvStream> m_pOutStream;
OUString aOrigURL;
DateTime aExpireTime;
SfxFrameWeakRef wLoadTargetFrame;
SvKeyValueIteratorRef xAttributes;
svtools::AsynchronLink aDoneLink;
uno::Sequence < util::RevisionTag > aVersions;
std::unique_ptr<::utl::TempFile> pTempFile;
uno::Reference<embed::XStorage> xStorage;
uno::Reference<embed::XStorage> m_xZipStorage;
uno::Reference<io::XInputStream> m_xInputStreamToLoadFrom;
uno::Reference<io::XInputStream> xInputStream;
uno::Reference<io::XStream> xStream;
uno::Reference<io::XStream> m_xLockingStream;
uno::Reference<task::XInteractionHandler> xInteraction;
ErrCode nLastStorageError;
OUString m_aBackupURL;
// the following member is changed and makes sense only during saving
// TODO/LATER: in future the signature state should be controlled by the medium not by the document
// in this case the member will hold this information
SignatureState m_nSignatureState;
bool m_bHasEmbeddedObjects = false;
util::DateTime m_aDateTime;
explicit SfxMedium_Impl();
~SfxMedium_Impl();
SfxMedium_Impl(const SfxMedium_Impl&) = delete;
SfxMedium_Impl& operator=(const SfxMedium_Impl&) = delete;
OUString getFilterMimeType()
{ return !m_pFilter ? OUString() : m_pFilter->GetMimeType(); }
};
SfxMedium_Impl::SfxMedium_Impl() :
m_nStorOpenMode(SFX_STREAM_READWRITE),
m_eError(ERRCODE_NONE),
bUpdatePickList(true),
bIsTemp( false ),
bDownloadDone( true ),
bIsStorage( false ),
bUseInteractionHandler( true ),
bAllowDefaultIntHdl( false ),
bDisposeStorage( false ),
bStorageBasedOnInStream( false ),
m_bSalvageMode( false ),
m_bVersionsAlreadyLoaded( false ),
m_bLocked( false ),
m_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),
aExpireTime( DateTime( DateTime::SYSTEM ) + static_cast<sal_Int32>(10) ),
nLastStorageError( ERRCODE_NONE ),
m_nSignatureState( SignatureState::NOSIGNATURES )
{
aDoneLink.CreateMutex();
}
SfxMedium_Impl::~SfxMedium_Impl()
{
aDoneLink.ClearPendingCall();
pTempFile.reset();
m_pSet.reset();
m_pURLObj.reset();
}
void SfxMedium::ResetError()
{
pImpl->m_eError = ERRCODE_NONE;
if( pImpl->m_pInStream )
pImpl->m_pInStream->ResetError();
if( pImpl->m_pOutStream )
pImpl->m_pOutStream->ResetError();
}
ErrCode const & SfxMedium::GetLastStorageCreationState()
{
return pImpl->nLastStorageError;
}
void SfxMedium::SetError(ErrCode nError)
{
pImpl->m_eError = nError;
}
ErrCode SfxMedium::GetErrorCode() const
{
ErrCode lError = pImpl->m_eError;
if(!lError && pImpl->m_pInStream)
lError = pImpl->m_pInStream->GetErrorCode();
if(!lError && pImpl->m_pOutStream)
lError = pImpl->m_pOutStream->GetErrorCode();
return lError;
}
void SfxMedium::CheckFileDate( const util::DateTime& aInitDate )
{
GetInitFileDate( true );
if ( pImpl->m_aDateTime.Seconds != aInitDate.Seconds
|| pImpl->m_aDateTime.Minutes != aInitDate.Minutes
|| pImpl->m_aDateTime.Hours != aInitDate.Hours
|| pImpl->m_aDateTime.Day != aInitDate.Day
|| pImpl->m_aDateTime.Month != aInitDate.Month
|| pImpl->m_aDateTime.Year != aInitDate.Year )
{
uno::Reference< task::XInteractionHandler > xHandler = GetInteractionHandler();
if ( xHandler.is() )
{
try
{
::rtl::Reference< ::ucbhelper::InteractionRequest > xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::makeAny(
document::ChangedByOthersRequest() ) );
uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations( 3 );
aContinuations[0] = new ::ucbhelper::InteractionAbort( xInteractionRequestImpl.get() );
aContinuations[1] = new ::ucbhelper::InteractionApprove( xInteractionRequestImpl.get() );
xInteractionRequestImpl->setContinuations( aContinuations );
xHandler->handle( xInteractionRequestImpl.get() );
::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = xInteractionRequestImpl->getSelection();
if ( uno::Reference< task::XInteractionAbort >( xSelected.get(), uno::UNO_QUERY ).is() )
{
SetError(ERRCODE_ABORT);
}
}
catch ( const uno::Exception& )
{}
}
}
}
bool SfxMedium::DocNeedsFileDateCheck() const
{
return ( !IsReadOnly() && ( GetURLObject().GetProtocol() == INetProtocol::File ||
GetURLObject().isAnyKnownWebDAVScheme() ) );
}
util::DateTime const & SfxMedium::GetInitFileDate( bool bIgnoreOldValue )
{
if ( ( bIgnoreOldValue || !pImpl->m_bGotDateTime ) && !pImpl->m_aLogicName.isEmpty() )
{
try
{
// add a default css::ucb::XCommandEnvironment
// in order to have the WebDAV UCP provider manage http/https authentication correctly
::ucbhelper::Content aContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ),
utl::UCBContentHelper::getDefaultCommandEnvironment(),
comphelper::getProcessComponentContext() );
aContent.getPropertyValue("DateModified") >>= pImpl->m_aDateTime;
pImpl->m_bGotDateTime = true;
}
catch ( const css::uno::Exception& )
{
}
}
return pImpl->m_aDateTime;
}
Reference < XContent > SfxMedium::GetContent() const
{
if ( !pImpl->aContent.get().is() )
{
Reference < css::ucb::XContent > xContent;
// tdf#95144 add a default css::ucb::XCommandEnvironment
// in order to have the WebDAV UCP provider manage https protocol certificates correctly
css:: uno::Reference< task::XInteractionHandler > xIH(
css::task::InteractionHandler::createWithParent( comphelper::getProcessComponentContext(), nullptr ) );
css::uno::Reference< css::ucb::XProgressHandler > xProgress;
::ucbhelper::CommandEnvironment* pCommandEnv = new ::ucbhelper::CommandEnvironment( new comphelper::SimpleFileAccessInteraction( xIH ), xProgress );
Reference < css::ucb::XCommandEnvironment > xEnv( static_cast< css::ucb::XCommandEnvironment* >(pCommandEnv), css::uno::UNO_QUERY );
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, xEnv, comphelper::getProcessComponentContext() );
}
catch ( const Exception& )
{
}
}
else
{
// TODO: SAL_WARN( "sfx.doc", "SfxMedium::GetContent()\nCreate Content? This code exists as fallback only. Please clarify, why its 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, xEnv, comphelper::getProcessComponentContext(), pImpl->aContent );
}
}
return pImpl->aContent.get();
}
OUString SfxMedium::GetBaseURL( bool bForSaving )
{
OUString aBaseURL;
const SfxStringItem* pBaseURLItem = GetItemSet()->GetItem<SfxStringItem>(SID_DOC_BASEURL);
if ( pBaseURLItem )
aBaseURL = pBaseURLItem->GetValue();
else if (!utl::ConfigManager::IsFuzzing() && GetContent().is())
{
try
{
Any aAny = pImpl->aContent.getPropertyValue("BaseURI");
aAny >>= aBaseURL;
}
catch ( const css::uno::Exception& )
{
}
if ( aBaseURL.isEmpty() )
aBaseURL = GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE );
}
if ( bForSaving )
{
SvtSaveOptions aOpt;
bool bIsRemote = IsRemote();
if( (bIsRemote && !aOpt.IsSaveRelINet()) || (!pImpl->m_bRemote && !aOpt.IsSaveRelFSys()) )
return OUString();
}
return aBaseURL;
}
bool SfxMedium::IsSkipImages()
{
const SfxStringItem* pSkipImagesItem = GetItemSet()->GetItem<SfxStringItem>(SID_FILE_FILTEROPTIONS);
return pSkipImagesItem && pSkipImagesItem->GetValue() == "SkipImages";
}
SvStream* SfxMedium::GetInStream()
{
if ( pImpl->m_pInStream )
return pImpl->m_pInStream.get();
if ( pImpl->pTempFile )
{
pImpl->m_pInStream.reset( new SvFileStream(pImpl->m_aName, pImpl->m_nStorOpenMode) );
pImpl->m_eError = pImpl->m_pInStream->GetError();
if (!pImpl->m_eError && (pImpl->m_nStorOpenMode & StreamMode::WRITE)
&& ! pImpl->m_pInStream->IsWritable() )
{
pImpl->m_eError = ERRCODE_IO_ACCESSDENIED;
pImpl->m_pInStream.reset();
}
else
return pImpl->m_pInStream.get();
}
GetMedium_Impl();
if ( GetError() )
return nullptr;
return pImpl->m_pInStream.get();
}
void SfxMedium::CloseInStream()
{
CloseInStream_Impl();
}
void SfxMedium::CloseInStream_Impl()
{
// 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() )
{
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->Flush();
else if( pImpl->m_pInStream )
pImpl->m_pInStream->Flush();
if ( GetError() == ERRCODE_NONE )
{
// does something only in case there is a temporary file ( means aName points to different location than aLogicName )
Transfer_Impl();
}
bool bResult = ( GetError() == ERRCODE_NONE );
if ( bResult && DocNeedsFileDateCheck() )
GetInitFileDate( true );
// remove truncation mode from the flags
pImpl->m_nStorOpenMode &= (~StreamMode::TRUNC);
return bResult;
}
bool SfxMedium::IsStorage()
{
if ( pImpl->xStorage.is() )
return true;
if ( pImpl->m_bTriedStorage )
return pImpl->bIsStorage;
if ( pImpl->pTempFile )
{
OUString aURL;
if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aURL )
== osl::FileBase::E_None )
{
SAL_WARN( "sfx.doc", "Physical name not convertible!");
}
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()
{
bool bPreview = false;
const SfxBoolItem* pPreview = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_PREVIEW, false);
if ( pPreview )
bPreview = pPreview->GetValue();
else
{
const SfxStringItem* pFlags = SfxItemSet::GetItem<SfxStringItem>(GetItemSet(), SID_OPTIONS, false);
if ( pFlags )
{
OUString aFileFlags = pFlags->GetValue();
aFileFlags = aFileFlags.toAsciiUpperCase();
if ( -1 != aFileFlags.indexOf( 'B' ) )
bPreview = true;
}
}
return bPreview;
}
void SfxMedium::StorageBackup_Impl()
{
::ucbhelper::Content aOriginalContent;
Reference< css::ucb::XCommandEnvironment > xDummyEnv;
bool bBasedOnOriginalFile = ( !pImpl->pTempFile && !( !pImpl->m_aLogicName.isEmpty() && pImpl->m_bSalvageMode )
&& !GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ).isEmpty()
&& GetURLObject().GetProtocol() == INetProtocol::File
&& ::utl::UCBContentHelper::IsDocument( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) );
if ( bBasedOnOriginalFile && pImpl->m_aBackupURL.isEmpty()
&& ::ucbhelper::Content::create( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext(), aOriginalContent ) )
{
DoInternalBackup_Impl( aOriginalContent );
if( pImpl->m_aBackupURL.isEmpty() )
SetError(ERRCODE_SFX_CANTCREATEBACKUP);
}
}
OUString const & SfxMedium::GetBackup_Impl()
{
if ( pImpl->m_aBackupURL.isEmpty() )
StorageBackup_Impl();
return pImpl->m_aBackupURL;
}
uno::Reference < embed::XStorage > SfxMedium::GetOutputStorage()
{
if ( GetError() )
return uno::Reference< embed::XStorage >();
// if the medium was constructed with a Storage: use this one, not a temp. storage
// if a temporary storage already exists: use it
if ( pImpl->xStorage.is() && ( pImpl->m_aLogicName.isEmpty() || pImpl->pTempFile ) )
return pImpl->xStorage;
// if necessary close stream that was used for reading
if ( pImpl->m_pInStream && !pImpl->m_pInStream->IsWritable() )
CloseInStream();
DBG_ASSERT( !pImpl->m_pOutStream, "OutStream in a readonly Medium?!" );
// TODO/LATER: The current solution is to store the document temporary and then copy it to the target location;
// in future it should be stored directly and then copied to the temporary location, since in this case no
// file attributes have to be preserved and system copying mechanics could be used instead of streaming.
CreateTempFileNoCopy();
return GetStorage();
}
void SfxMedium::SetEncryptionDataToStorage_Impl()
{
// in case media-descriptor contains password it should be used on opening
if ( pImpl->xStorage.is() && pImpl->m_pSet )
{
uno::Sequence< beans::NamedValue > aEncryptionData;
if ( GetEncryptionData_Impl( pImpl->m_pSet.get(), aEncryptionData ) )
{
// replace the password with encryption data
pImpl->m_pSet->ClearItem( SID_PASSWORD );
pImpl->m_pSet->Put( SfxUnoAnyItem( SID_ENCRYPTIONDATA, uno::makeAny( aEncryptionData ) ) );
try
{
::comphelper::OStorageHelper::SetCommonStorageEncryptionData( pImpl->xStorage, aEncryptionData );
}
catch( const uno::Exception& )
{
SAL_WARN( "sfx.doc", "It must be possible to set a common password for the storage" );
// TODO/LATER: set the error code in case of problem
// SetError(ERRCODE_IO_GENERAL);
}
}
}
}
#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
// FIXME: Hmm actually lock files should be used for sftp: documents
// even if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT. Only the use of lock
// files for *local* documents is unnecessary in that case. But
// actually, the checks for sftp: here are just wishful thinking; I
// don't this there is any support for actually editing documents
// behind sftp: URLs anyway.
// Sure, there could perhaps be a 3rd-party extension that brings UCB
// the potential to handle files behind sftp:. But there could also be
// an extension that handles some arbitrary foobar: scheme *and* it
// could be that lock files would be the correct thing to use for
// foobar: documents, too. But the hardcoded test below won't know
// that. Clearly the knowledge whether lock files should be used or
// not for some URL scheme belongs in UCB, not here.
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 ( ::svt::DocumentLockFile::IsInteractionAllowed() && xHandler.is() && ( bIsLoading || !bHandleSysLocked || bOwnLock ) )
{
OUString aDocumentURL = GetURLObject().GetLastName();
OUString aInfo;
::rtl::Reference< ::ucbhelper::InteractionRequest > xInteractionRequestImpl;
sal_Int32 nContinuations = 3;
if ( bOwnLock )
{
aInfo = aData[LockFileComponent::EDITTIME];
xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::makeAny(
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() && !aData[LockFileComponent::EDITTIME].isEmpty() )
aInfo += " ( " + aData[LockFileComponent::EDITTIME] + " )";
if (!bIsLoading) // so, !bHandleSysLocked
{
xInteractionRequestImpl = new ::ucbhelper::InteractionRequest(uno::makeAny(
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::makeAny(
document::LockedDocumentRequest( OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo ) ) );
}
}
uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations(nContinuations);
aContinuations[0] = new ::ucbhelper::InteractionAbort( xInteractionRequestImpl.get() );
aContinuations[1] = new ::ucbhelper::InteractionApprove( xInteractionRequestImpl.get() );
aContinuations[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
aContinuations[3] = new ::ucbhelper::InteractionRetry(xInteractionRequestImpl.get());
}
xInteractionRequestImpl->setContinuations( aContinuations );
xHandler->handle( xInteractionRequestImpl.get() );
::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 ( XSelected == aContinuations[1] )
{
// 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::makeAny( document::LockFileIgnoreRequest() ));
break;
case MessageDlg::LockFileCorrupt:
xIgnoreRequestImpl = new ::ucbhelper::InteractionRequest(uno::makeAny( document::LockFileCorruptRequest() ));
break;
}
uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations(2);
aContinuations[0] = new ::ucbhelper::InteractionAbort(xIgnoreRequestImpl.get());
aContinuations[1] = new ::ucbhelper::InteractionApprove(xIgnoreRequestImpl.get());
xIgnoreRequestImpl->setContinuations(aContinuations);
xHandler->handle(xIgnoreRequestImpl.get());
::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = xIgnoreRequestImpl->getSelection();
bool bReadOnly = uno::Reference< task::XInteractionApprove >(xSelected.get(), uno::UNO_QUERY).is();
if (bReadOnly)
{
GetItemSet()->Put(SfxBoolItem(SID_DOC_READONLY, true));
}
else
{
SetError(ERRCODE_ABORT);
}
return bReadOnly;
}
return false;
}
namespace
{
bool isSuitableProtocolForLocking(const OUString & rLogicName)
{
INetURLObject aUrl( rLogicName );
INetProtocol eProt = aUrl.GetProtocol();
#if !HAVE_FEATURE_MACOSX_SANDBOX
if (eProt == INetProtocol::File) {
return true;
}
#endif
return eProt == INetProtocol::Smb || eProt == INetProtocol::Sftp;
}
}
#endif // HAVE_FEATURE_MULTIUSER_ENVIRONMENT
// sets SID_DOC_READONLY if the document cannot be opened for editing
// if user cancel the loading the ERROR_ABORT is set
SfxMedium::LockFileResult SfxMedium::LockOrigFileOnDemand( bool bLoading, bool bNoUI, bool bTryIgnoreLockFile )
{
#if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT
(void) bLoading;
(void) bNoUI;
(void) bTryIgnoreLockFile;
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() )
{
try
{
bool bResult = pImpl->m_bLocked;
bool bIsTemplate = false;
// so, this is webdav stuff...
if ( !bResult )
{
// no read-write access is necessary on loading if the document is explicitly opened as copy
const SfxBoolItem* pTemplateItem = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_TEMPLATE, false);
bIsTemplate = ( bLoading && pTemplateItem && pTemplateItem->GetValue() );
}
if ( !bIsTemplate && !bResult && !IsReadOnly() )
{
ShowLockResult bUIStatus = ShowLockResult::NoLock;
do
{
if( !bResult )
{
uno::Reference< task::XInteractionHandler > xCHandler = GetInteractionHandler( true );
Reference< css::ucb::XCommandEnvironment > xComEnv = new ::ucbhelper::CommandEnvironment(
xCHandler, Reference< css::ucb::XProgressHandler >() );
ucbhelper::Content aContentToLock(
GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ),
xComEnv, comphelper::getProcessComponentContext() );
try
{
aContentToLock.lock();
bResult = true;
}
catch ( ucb::InteractiveLockingLockedException& )
{
// received when the resource is already locked
// get the lock owner, using a special ucb.webdav property
// the owner property retrieved here is what the other principal send the server
// when activating the lock.
// See http://tools.ietf.org/html/rfc4918#section-14.17 for details
LockFileEntry aLockData;
aLockData[LockFileComponent::OOOUSERNAME] = "Unknown user";
// This solution works right when the LO user name and the WebDAV user
// name are the same.
// A better thing to do would be to obtain the 'real' WebDAV user name,
// but that's not possible from a WebDAV UCP provider client.
LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry();
// use the current LO user name as the system name
aLockData[LockFileComponent::SYSUSERNAME] = aOwnData[LockFileComponent::SYSUSERNAME];
uno::Sequence< css::ucb::Lock > aLocks;
// getting the property, send a PROPFIND to the server over the net
if( aContentToLock.getPropertyValue( "DAV:lockdiscovery" ) >>= aLocks )
{
// got at least a lock, show the owner of the first lock returned
css::ucb::Lock aLock = aLocks[0];
OUString aOwner;
if(aLock.Owner >>= aOwner)
{
// we need to display the WebDAV user name owning the lock, not the local one
aLockData[LockFileComponent::OOOUSERNAME] = aOwner;
}
}
if ( !bResult && !bNoUI )
{
bUIStatus = ShowLockedDocumentDialog( aLockData, bLoading, false , true );
}
}
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& )
{}
}
} while( !bResult && bUIStatus == ShowLockResult::Try );
}
pImpl->m_bLocked = bResult;
if ( !bResult && GetError() == ERRCODE_NONE )
{
// the error should be set in case it is storing process
// or the document has been opened for editing explicitly
const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
if ( !bLoading || (pReadOnlyItem && !pReadOnlyItem->GetValue()) )
SetError(ERRCODE_IO_ACCESSDENIED);
else
GetItemSet()->Put( SfxBoolItem( SID_DOC_READONLY, true ) );
}
// when the file is locked, get the current file date
if ( bResult && DocNeedsFileDateCheck() )
GetInitFileDate( true );
if ( bResult )
eResult = LockFileResult::Succeeded;
}
catch ( const uno::Exception& )
{
SAL_WARN( "sfx.doc", "Locking exception: WebDAV while trying to lock the file" );
}
return eResult;
}
if (!IsLockingUsed())
return LockFileResult::Succeeded;
if (GetURLObject().HasError())
return eResult;
try
{
if ( pImpl->m_bLocked && bLoading
&& GetURLObject().GetProtocol() == INetProtocol::File )
{
// if the document is already locked the system locking might be temporarily off after storing
// check whether the system file locking should be taken again
GetLockingStream_Impl();
}
bool bResult = pImpl->m_bLocked;
if ( !bResult )
{
// no read-write access is necessary on loading if the document is explicitly opened as copy
const SfxBoolItem* pTemplateItem = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_TEMPLATE, false);
bResult = ( bLoading && pTemplateItem && pTemplateItem->GetValue() );
}
if ( !bResult && !IsReadOnly() )
{
bool bContentReadonly = false;
if ( bLoading && GetURLObject().GetProtocol() == INetProtocol::File )
{
// let the original document be opened to check the possibility to open it for editing
// and to let the writable stream stay open to hold the lock on the document
GetLockingStream_Impl();
}
// "IsReadOnly" property does not allow to detect whether the file is readonly always
// so we try always to open the file for editing
// the file is readonly only in case the read-write stream can not be opened
if ( bLoading && !pImpl->m_xLockingStream.is() )
{
try
{
// MediaDescriptor does this check also, the duplication should be avoided in future
Reference< css::ucb::XCommandEnvironment > xDummyEnv;
::ucbhelper::Content aContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext() );
aContent.getPropertyValue("IsReadOnly") >>= bContentReadonly;
}
catch( const uno::Exception& ) {}
}
// do further checks only if the file not readonly in fs
if ( !bContentReadonly )
{
// the special file locking should be used only for suitable URLs
if ( isSuitableProtocolForLocking( pImpl->m_aLogicName ) )
{
// in case of storing the document should request the output before locking
if ( bLoading )
{
// let the stream be opened to check the system file locking
GetMedium_Impl();
if (GetError() != ERRCODE_NONE) {
return eResult;
}
}
ShowLockResult bUIStatus = ShowLockResult::NoLock;
// check whether system file locking has been used, the default value is false
bool bUseSystemLock = comphelper::isFileUrl( pImpl->m_aLogicName ) && IsSystemFileLockingUsed();
// TODO/LATER: This implementation does not allow to detect the system lock on saving here, actually this is no big problem
// if system lock is used the writeable stream should be available
bool bHandleSysLocked = ( bLoading && bUseSystemLock && !pImpl->xStream.is() && !pImpl->m_pOutStream );
// The file is attempted to get locked for the duration of lockfile creation on save
std::unique_ptr<osl::File> pFileLock;
if (!bLoading && bUseSystemLock && pImpl->pTempFile)
{
INetURLObject aDest(GetURLObject());
OUString aDestURL(aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE));
if (comphelper::isFileUrl(aDestURL) || !aDest.removeSegment())
{
pFileLock = o3tl::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 );
bool bIoErr = false;
if (!bHandleSysLocked)
{
try
{
bResult = aLockFile.CreateOwnLockFile();
}
catch (const uno::Exception&)
{
if (tools::IsMappedWebDAVPath(GetURLObject()))
{
// 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 ( !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();
}
else if (bLoading && !bHandleSysLocked)
eResult = LockFileResult::FailedLockFile;
}
}
}
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;
}
}
}
if ( !bResult && GetError() == ERRCODE_NONE )
{
// the error should be set in case it is storing process
// or the document has been opened for editing explicitly
const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
if ( !bLoading || (pReadOnlyItem && !pReadOnlyItem->GetValue()) )
SetError(ERRCODE_IO_ACCESSDENIED);
else
GetItemSet()->Put( SfxBoolItem( SID_DOC_READONLY, true ) );
}
// when the file is locked, get the current file date
if ( bResult && DocNeedsFileDateCheck() )
GetInitFileDate( true );
if ( bResult )
eResult = LockFileResult::Succeeded;
}
catch( const uno::Exception& )
{
SAL_WARN( "sfx.doc", "Locking exception: high probability, that the content has not been created" );
}
return eResult;
#endif
}
uno::Reference < embed::XStorage > SfxMedium::GetStorage( bool bCreateTempIfNo )
{
if ( pImpl->xStorage.is() || pImpl->m_bTriedStorage )
return pImpl->xStorage;
uno::Sequence< uno::Any > aArgs( 2 );
// the medium should be retrieved before temporary file creation
// to let the MediaDescriptor be filled with the streams
GetMedium_Impl();
if ( bCreateTempIfNo )
CreateTempFile( false );
GetMedium_Impl();
if ( GetError() )
return pImpl->xStorage;
const SfxBoolItem* pRepairItem = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_REPAIRPACKAGE, false);
if ( pRepairItem && pRepairItem->GetValue() )
{
// the storage should be created for repairing mode
CreateTempFile( false );
GetMedium_Impl();
Reference< css::ucb::XProgressHandler > xProgressHandler;
Reference< css::task::XStatusIndicator > xStatusIndicator;
const SfxUnoAnyItem* pxProgressItem = SfxItemSet::GetItem<SfxUnoAnyItem>(GetItemSet(), SID_PROGRESS_STATUSBAR_CONTROL, false);
if( pxProgressItem && ( pxProgressItem->GetValue() >>= xStatusIndicator ) )
xProgressHandler.set( new utl::ProgressHandlerWrap( xStatusIndicator ) );
uno::Sequence< beans::PropertyValue > aAddProps( 2 );
aAddProps[0].Name = "RepairPackage";
aAddProps[0].Value <<= true;
aAddProps[1].Name = "StatusIndicator";
aAddProps[1].Value <<= xProgressHandler;
// the first arguments will be filled later
aArgs.realloc( 3 );
aArgs[2] <<= aAddProps;
}
if ( pImpl->xStream.is() )
{
// since the storage is based on temporary stream we open it always read-write
aArgs[0] <<= pImpl->xStream;
aArgs[1] <<= embed::ElementModes::READWRITE;
pImpl->bStorageBasedOnInStream = true;
if (pImpl->m_bDisableFileSync)
{
// Forward NoFileSync to the storage factory.
aArgs.realloc(3);
uno::Sequence<beans::PropertyValue> aProperties(
comphelper::InitPropertySequence({ { "NoFileSync", uno::makeAny(true) } }));
aArgs[2] <<= aProperties;
}
}
else if ( pImpl->xInputStream.is() )
{
// since the storage is based on temporary stream we open it always read-write
aArgs[0] <<= pImpl->xInputStream;
aArgs[1] <<= embed::ElementModes::READ;
pImpl->bStorageBasedOnInStream = true;
}
else
{
CloseStreams_Impl();
aArgs[0] <<= pImpl->m_aName;
aArgs[1] <<= embed::ElementModes::READ;
pImpl->bStorageBasedOnInStream = false;
}
try
{
pImpl->xStorage.set( ::comphelper::OStorageHelper::GetStorageFactory()->createInstanceWithArguments( aArgs ),
uno::UNO_QUERY );
}
catch( const uno::Exception& )
{
// impossibility to create the storage is no error
}
if( ( pImpl->nLastStorageError = GetError() ) != ERRCODE_NONE )
{
pImpl->xStorage = nullptr;
if ( pImpl->m_pInStream )
pImpl->m_pInStream->Seek(0);
return uno::Reference< embed::XStorage >();
}
pImpl->m_bTriedStorage = true;
// TODO/LATER: Get versionlist on demand
if ( pImpl->xStorage.is() )
{
SetEncryptionDataToStorage_Impl();
GetVersionList();
}
const SfxInt16Item* pVersion = SfxItemSet::GetItem<SfxInt16Item>(pImpl->m_pSet.get(), SID_VERSION, false);
bool bResetStorage = false;
if ( pVersion && pVersion->GetValue() )
{
// Read all available versions
if ( pImpl->aVersions.getLength() )
{
// 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 ? pVersion->GetValue() : 0;
if ( nVersion<0 )
nVersion = static_cast<short>(pImpl->aVersions.getLength()) + nVersion;
else if ( nVersion )
nVersion--;
util::RevisionTag& rTag = pImpl->aVersions[nVersion];
{
// Open SubStorage for all versions
uno::Reference < embed::XStorage > xSub = pImpl->xStorage->openStorageElement( "Versions",
embed::ElementModes::READ );
DBG_ASSERT( xSub.is(), "Version list, but no Versions!" );
// There the version is stored as packed Stream
uno::Reference < io::XStream > xStr = xSub->openStreamElement( rTag.Identifier, embed::ElementModes::READ );
std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream( xStr ));
if ( pStream && pStream->GetError() == ERRCODE_NONE )
{
// Unpack Stream in TempDir
::utl::TempFile aTempFile;
OUString aTmpName = aTempFile.GetURL();
SvFileStream aTmpStream( aTmpName, SFX_STREAM_READWRITE );
pStream->ReadStream( aTmpStream );
pStream.reset();
aTmpStream.Close();
// Open data as Storage
pImpl->m_nStorOpenMode = SFX_STREAM_READONLY;
pImpl->xStorage = comphelper::OStorageHelper::GetStorageFromURL( aTmpName, embed::ElementModes::READ );
pImpl->bStorageBasedOnInStream = false;
OUString aTemp;
osl::FileBase::getSystemPathFromFileURL( aTmpName, aTemp );
SetPhysicalName_Impl( aTemp );
pImpl->bIsTemp = true;
GetItemSet()->Put( SfxBoolItem( SID_DOC_READONLY, true ) );
// TODO/MBA
pImpl->aVersions.realloc(0);
}
else
bResetStorage = true;
}
}
else
bResetStorage = true;
}
if ( bResetStorage )
{
pImpl->xStorage.clear();
if ( pImpl->m_pInStream )
pImpl->m_pInStream->Seek( 0 );
}
pImpl->bIsStorage = pImpl->xStorage.is();
return pImpl->xStorage;
}
uno::Reference< embed::XStorage > const & SfxMedium::GetZipStorageToSign_Impl( bool bReadOnly )
{
if ( !GetError() && !pImpl->m_xZipStorage.is() )
{
GetMedium_Impl();
try
{
// we can not sign document if there is no stream
// should it be possible at all?
if ( !bReadOnly && pImpl->xStream.is() )
{
pImpl->m_xZipStorage = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream( ZIP_STORAGE_FORMAT_STRING, pImpl->xStream );
}
else if ( pImpl->xInputStream.is() )
{
pImpl->m_xZipStorage = ::comphelper::OStorageHelper::GetStorageOfFormatFromInputStream( ZIP_STORAGE_FORMAT_STRING, pImpl->xInputStream );
}
}
catch( const uno::Exception& )
{
SAL_WARN( "sfx.doc", "No possibility to get readonly version of storage from medium!" );
}
if ( GetError() ) // do not remove warnings
ResetError();
}
return pImpl->m_xZipStorage;
}
void SfxMedium::CloseZipStorage_Impl()
{
if ( pImpl->m_xZipStorage.is() )
{
try {
pImpl->m_xZipStorage->dispose();
} catch( const uno::Exception& )
{}
pImpl->m_xZipStorage.clear();
}
}
void SfxMedium::CloseStorage()
{
if ( pImpl->xStorage.is() )
{
uno::Reference < lang::XComponent > xComp( pImpl->xStorage, uno::UNO_QUERY );
// in the salvage mode the medium does not own the storage
if ( pImpl->bDisposeStorage && !pImpl->m_bSalvageMode )
{
try {
xComp->dispose();
} catch( const uno::Exception& )
{
SAL_WARN( "sfx.doc", "Medium's storage is already disposed!" );
}
}
pImpl->xStorage.clear();
pImpl->bStorageBasedOnInStream = false;
}
pImpl->m_bTriedStorage = false;
pImpl->bIsStorage = false;
}
void SfxMedium::CanDisposeStorage_Impl( bool bDisposeStorage )
{
pImpl->bDisposeStorage = bDisposeStorage;
}
bool SfxMedium::WillDisposeStorageOnClose_Impl()
{
return pImpl->bDisposeStorage;
}
StreamMode SfxMedium::GetOpenMode() const
{
return pImpl->m_nStorOpenMode;
}
void SfxMedium::SetOpenMode( StreamMode nStorOpen,
bool bDontClose )
{
if ( pImpl->m_nStorOpenMode != nStorOpen )
{
pImpl->m_nStorOpenMode = nStorOpen;
if( !bDontClose )
{
if ( pImpl->xStorage.is() )
CloseStorage();
CloseStreams_Impl();
}
}
}
bool SfxMedium::UseBackupToRestore_Impl( ::ucbhelper::Content& aOriginalContent,
const Reference< css::ucb::XCommandEnvironment >& xComEnv )
{
try
{
::ucbhelper::Content aTransactCont( pImpl->m_aBackupURL, xComEnv, comphelper::getProcessComponentContext() );
Reference< XInputStream > aOrigInput = aTransactCont.openStream();
aOriginalContent.writeStream( aOrigInput, true );
return true;
}
catch( const Exception& )
{
// in case of failure here the backup file should not be removed
// TODO/LATER: a message should be used to let user know about the backup
pImpl->m_bRemoveBackup = false;
// TODO/LATER: needs a specific error code
pImpl->m_eError = ERRCODE_IO_GENERAL;
}
return false;
}
bool SfxMedium::StorageCommit_Impl()
{
bool bResult = false;
Reference< css::ucb::XCommandEnvironment > xDummyEnv;
::ucbhelper::Content aOriginalContent;
if ( pImpl->xStorage.is() )
{
if ( !GetError() )
{
uno::Reference < embed::XTransactedObject > xTrans( pImpl->xStorage, uno::UNO_QUERY );
if ( xTrans.is() )
{
try
{
xTrans->commit();
CloseZipStorage_Impl();
bResult = true;
}
catch ( const embed::UseBackupException& aBackupExc )
{
// since the temporary file is created always now, the scenario is close to be impossible
if ( !pImpl->pTempFile )
{
OSL_ENSURE( !pImpl->m_aBackupURL.isEmpty(), "No backup on storage commit!" );
if ( !pImpl->m_aBackupURL.isEmpty()
&& ::ucbhelper::Content::create( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ),
xDummyEnv, comphelper::getProcessComponentContext(),
aOriginalContent ) )
{
// use backup to restore the file
// the storage has already disconnected from original location
CloseAndReleaseStreams_Impl();
if ( !UseBackupToRestore_Impl( aOriginalContent, xDummyEnv ) )
{
// connect the medium to the temporary file of the storage
pImpl->aContent = ::ucbhelper::Content();
pImpl->m_aName = aBackupExc.TemporaryFileURL;
OSL_ENSURE( !pImpl->m_aName.isEmpty(), "The exception _must_ contain the temporary URL!" );
}
}
}
if (!GetError())
SetError(ERRCODE_IO_GENERAL);
}
catch ( const uno::Exception& )
{
//TODO/LATER: improve error handling
SetError(ERRCODE_IO_GENERAL);
}
}
}
}
return bResult;
}
void SfxMedium::TransactedTransferForFS_Impl( const INetURLObject& aSource,
const INetURLObject& aDest,
const Reference< css::ucb::XCommandEnvironment >& xComEnv )
{
bool bResult = false;
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() )
{
if ( pImpl->xStorage.is() )
CloseStorage();
CloseStreams_Impl();
::ucbhelper::Content aTempCont;
if( ::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext(), aTempCont ) )
{
bool bTransactStarted = false;
const SfxBoolItem* pOverWrite = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_OVERWRITE, false);
bool bOverWrite = !pOverWrite || pOverWrite->GetValue();
try
{
OUString aSourceMainURL = aSource.GetMainURL(INetURLObject::DecodeMechanism::NONE);
OUString aDestMainURL = aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE);
sal_uInt64 nAttributes = GetDefaultFileAttributes(aDestMainURL);
if (IsFileMovable(aDestMainURL) && osl::File::move(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 (bOverWrite && ::utl::UCBContentHelper::IsDocument(aDestMainURL))
{
if( pImpl->m_aBackupURL.isEmpty() )
DoInternalBackup_Impl( aOriginalContent );
if( !pImpl->m_aBackupURL.isEmpty() )
{
Reference< XInputStream > aTempInput = aTempCont.openStream();
bTransactStarted = true;
aOriginalContent.setPropertyValue( "Size", uno::makeAny( sal_Int64(0) ) );
aOriginalContent.writeStream( aTempInput, bOverWrite );
bResult = true;
}
else
{
pImpl->m_eError = ERRCODE_SFX_CANTCREATEBACKUP;
}
}
else
{
Reference< XInputStream > aTempInput = aTempCont.openStream();
aOriginalContent.writeStream( aTempInput, bOverWrite );
bResult = true;
}
}
}
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;
}
if ( bResult )
{
if ( pImpl->pTempFile )
{
pImpl->pTempFile->EnableKillingFile();
pImpl->pTempFile.reset();
}
}
else if ( bTransactStarted )
{
UseBackupToRestore_Impl( aOriginalContent, xDummyEnv );
}
}
else
pImpl->m_eError = ERRCODE_IO_CANTREAD;
}
}
bool SfxMedium::TryDirectTransfer( const OUString& aURL, SfxItemSet const & aTargetSet )
{
if ( GetError() )
return false;
// if the document had no password it should be stored without password
// if the document had password it should be stored with the same password
// otherwise the stream copying can not be done
const SfxStringItem* pNewPassItem = aTargetSet.GetItem<SfxStringItem>(SID_PASSWORD, false);
const SfxStringItem* pOldPassItem = SfxItemSet::GetItem<SfxStringItem>(GetItemSet(), SID_PASSWORD, false);
if ( ( !pNewPassItem && !pOldPassItem )
|| ( pNewPassItem && pOldPassItem && pNewPassItem->GetValue() == pOldPassItem->GetValue() ) )
{
// the filter must be the same
const SfxStringItem* pNewFilterItem = aTargetSet.GetItem<SfxStringItem>(SID_FILTER_NAME, false);
const SfxStringItem* pOldFilterItem = SfxItemSet::GetItem<SfxStringItem>(GetItemSet(), SID_FILTER_NAME, false);
if ( pNewFilterItem && pOldFilterItem && pNewFilterItem->GetValue() == pOldFilterItem->GetValue() )
{
// get the input stream and copy it
// in case of success return true
uno::Reference< io::XInputStream > xInStream = GetInputStream();
ResetError();
if ( xInStream.is() )
{
try
{
uno::Reference< io::XSeekable > xSeek( xInStream, uno::UNO_QUERY );
sal_Int64 nPos = 0;
if ( xSeek.is() )
{
nPos = xSeek->getPosition();
xSeek->seek( 0 );
}
uno::Reference < css::ucb::XCommandEnvironment > xEnv;
::ucbhelper::Content aTargetContent( aURL, xEnv, comphelper::getProcessComponentContext() );
InsertCommandArgument aInsertArg;
aInsertArg.Data = xInStream;
const SfxBoolItem* pOverWrite = aTargetSet.GetItem<SfxBoolItem>(SID_OVERWRITE, false);
if ( pOverWrite && !pOverWrite->GetValue() ) // argument says: never overwrite
aInsertArg.ReplaceExisting = false;
else
aInsertArg.ReplaceExisting = true; // default is overwrite existing files
Any aCmdArg;
aCmdArg <<= aInsertArg;
aTargetContent.executeCommand( "insert",
aCmdArg );
if ( xSeek.is() )
xSeek->seek( nPos );
return true;
}
catch( const uno::Exception& )
{}
}
}
}
return false;
}
void SfxMedium::Transfer_Impl()
{
// The transfer is required only in two cases: either if there is a temporary file or if there is a salvage item
OUString aNameURL;
if ( pImpl->pTempFile )
aNameURL = pImpl->pTempFile->GetURL();
else if ( !pImpl->m_aLogicName.isEmpty() && pImpl->m_bSalvageMode )
{
// makes sense only in case logic name is set
if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aNameURL )
!= osl::FileBase::E_None )
SAL_WARN( "sfx.doc", "The medium name is not convertible!" );
}
if ( !aNameURL.isEmpty() && ( !pImpl->m_eError || pImpl->m_eError.IsWarning() ) )
{
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;
Reference< css::task::XInteractionHandler > xInteractionHandler = GetInteractionHandler();
if (xInteractionHandler.is())
xComEnv = new ::ucbhelper::CommandEnvironment( xInteractionHandler,
Reference< css::ucb::XProgressHandler >() );
OUString aDestURL( aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
if ( comphelper::isFileUrl( aDestURL ) || !aDest.removeSegment() )
{
TransactedTransferForFS_Impl( aSource, aDest, xComEnv );
if (!pImpl->m_bDisableFileSync)
{
// Hideous - no clean way to do this, so we re-open the file just to fsync it
osl::File aFile( aDestURL );
if ( aFile.open( osl_File_OpenFlag_Write ) == osl::FileBase::E_None )
{
aFile.sync();
SAL_INFO( "sfx.doc", "fsync'd saved file '" << aDestURL << "'" );
aFile.close();
}
}
}
else
{
// create content for the parent folder and call transfer on that content with the source content
// and the destination file name as parameters
::ucbhelper::Content aSourceContent;
::ucbhelper::Content aTransferContent;
::ucbhelper::Content aDestContent;
(void)::ucbhelper::Content::create( aDestURL, xComEnv, comphelper::getProcessComponentContext(), aDestContent );
// For checkin, we need the object URL, not the parent folder:
if ( !IsInCheckIn( ) )
{
// Get the parent URL from the XChild if possible: why would the URL necessarily have
// a hierarchical path? It's not always the case for CMIS.
Reference< css::container::XChild> xChild( aDestContent.get(), uno::UNO_QUERY );
OUString sParentUrl;
if ( xChild.is( ) )
{
Reference< css::ucb::XContent > xParent( xChild->getParent( ), uno::UNO_QUERY );
if ( xParent.is( ) )
{
sParentUrl = xParent->getIdentifier( )->getContentIdentifier();
}
}
if ( sParentUrl.isEmpty() )
aDestURL = aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE );
// adjust to above aDest.removeSegment()
else
aDestURL = sParentUrl;
}
// LongName wasn't defined anywhere, only used here... get the Title instead
// as it's less probably empty
OUString aFileName;
Any aAny = aDestContent.getPropertyValue("Title");
aAny >>= aFileName;
aAny = aDestContent.getPropertyValue( "ObjectId" );
OUString sObjectId;
aAny >>= sObjectId;
if ( aFileName.isEmpty() )
aFileName = GetURLObject().getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
try
{
aTransferContent = ::ucbhelper::Content( aDestURL, xComEnv, comphelper::getProcessComponentContext() );
}
catch (const css::ucb::ContentCreationException& ex)
{
pImpl->m_eError = ERRCODE_IO_GENERAL;
if (
(ex.eError == css::ucb::ContentCreationError_NO_CONTENT_PROVIDER ) ||
(ex.eError == css::ucb::ContentCreationError_CONTENT_CREATION_FAILED)
)
{
pImpl->m_eError = ERRCODE_IO_NOTEXISTSPATH;
}
}
catch (const css::uno::Exception&)
{
pImpl->m_eError = ERRCODE_IO_GENERAL;
}
if ( !pImpl->m_eError || pImpl->m_eError.IsWarning() )
{
// free resources, otherwise the transfer may fail
if ( pImpl->xStorage.is() )
CloseStorage();
CloseStreams_Impl();
(void)::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aSourceContent );
// check for external parameters that may customize the handling of NameClash situations
const SfxBoolItem* pOverWrite = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_OVERWRITE, false);
sal_Int32 nNameClash;
if ( pOverWrite && !pOverWrite->GetValue() )
// argument says: never overwrite
nNameClash = NameClash::ERROR;
else
// default is overwrite existing files
nNameClash = NameClash::OVERWRITE;
try
{
OUString aMimeType = pImpl->getFilterMimeType();
::ucbhelper::InsertOperation eOperation = ::ucbhelper::InsertOperation::Copy;
bool bMajor = false;
OUString sComment;
if ( IsInCheckIn( ) )
{
eOperation = ::ucbhelper::InsertOperation::Checkin;
const SfxBoolItem* pMajor = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_DOCINFO_MAJOR, false);
bMajor = pMajor && pMajor->GetValue( );
const SfxStringItem* pComments = SfxItemSet::GetItem<SfxStringItem>(GetItemSet(), SID_DOCINFO_COMMENTS, false);
if ( pComments )
sComment = pComments->GetValue( );
}
OUString sResultURL;
aTransferContent.transferContent(
aSourceContent, eOperation,
aFileName, nNameClash, aMimeType, bMajor, sComment,
&sResultURL, sObjectId );
if ( !sResultURL.isEmpty( ) ) // Likely to happen only for checkin
SwitchDocumentToFile( sResultURL );
try
{
if ( GetURLObject().isAnyKnownWebDAVScheme() &&
eOperation == ::ucbhelper::InsertOperation::Copy )
{
// tdf#95272 try to re-issue a lock command when a new file is created.
// This may be needed because some WebDAV servers fail to implement the
// 'LOCK on unallocated reference', see issue comment:
// <https://bugs.documentfoundation.org/show_bug.cgi?id=95792#c8>
// and specification at:
// <http://tools.ietf.org/html/rfc4918#section-7.3>
// If the WebDAV resource is already locked by this LO instance, nothing will
// happen, e.g. the LOCK method will not be sent to the server.
::ucbhelper::Content aLockContent = ::ucbhelper::Content( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
aLockContent.lock();
}
}
catch ( css::uno::Exception & e )
{
SAL_WARN( "sfx.doc", "LOCK not working while re-issuing it. Exception message: " << e );
}
}
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,
const OUString& aPrefix,
const OUString& aExtension,
const OUString& aDestDir )
{
if ( !pImpl->m_aBackupURL.isEmpty() )
return; // the backup was done already
::utl::TempFile aTransactTemp( aPrefix, true, &aExtension, &aDestDir );
INetURLObject aBackObj( aTransactTemp.GetURL() );
OUString aBackupName = aBackObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
Reference < css::ucb::XCommandEnvironment > xDummyEnv;
::ucbhelper::Content aBackupCont;
if( ::ucbhelper::Content::create( aDestDir, xDummyEnv, comphelper::getProcessComponentContext(), aBackupCont ) )
{
try
{
OUString sMimeType = pImpl->getFilterMimeType();
aBackupCont.transferContent( aOriginalContent,
::ucbhelper::InsertOperation::Copy,
aBackupName,
NameClash::OVERWRITE,
sMimeType );
pImpl->m_aBackupURL = aBackObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
pImpl->m_bRemoveBackup = true;
}
catch( const Exception& )
{}
}
if ( pImpl->m_aBackupURL.isEmpty() )
aTransactTemp.EnableKillingFile();
}
void SfxMedium::DoInternalBackup_Impl( const ::ucbhelper::Content& aOriginalContent )
{
if ( !pImpl->m_aBackupURL.isEmpty() )
return; // the backup was done already
OUString aFileName = GetURLObject().getName( INetURLObject::LAST_SEGMENT,
true,
INetURLObject::DecodeMechanism::NONE );
sal_Int32 nPrefixLen = aFileName.lastIndexOf( '.' );
OUString aPrefix = ( nPrefixLen == -1 ) ? aFileName : aFileName.copy( 0, nPrefixLen );
OUString aExtension = ( nPrefixLen == -1 ) ? OUString() : aFileName.copy( nPrefixLen );
OUString aBakDir = SvtPathOptions().GetBackupPath();
// create content for the parent folder ( = backup folder )
::ucbhelper::Content aContent;
Reference < css::ucb::XCommandEnvironment > xEnv;
if( ::utl::UCBContentHelper::ensureFolder(comphelper::getProcessComponentContext(), xEnv, aBakDir, aContent) )
DoInternalBackup_Impl( aOriginalContent, aPrefix, aExtension, aBakDir );
if ( pImpl->m_aBackupURL.isEmpty() )
{
// the copiing to the backup catalog failed ( for example because
// of using an encrypted partition as target catalog )
// since the user did not specify to make backup explicitly
// office should try to make backup in another place,
// target catalog does not look bad for this case ( and looks
// to be the only way for encrypted partitions )
INetURLObject aDest = GetURLObject();
if ( aDest.removeSegment() )
DoInternalBackup_Impl( aOriginalContent, aPrefix, aExtension, aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
}
}
void SfxMedium::DoBackup_Impl()
{
// source file name is the logical name of this medium
INetURLObject aSource( GetURLObject() );
// there is nothing to backup in case source file does not exist
if ( !::utl::UCBContentHelper::IsDocument( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
return;
bool bSuccess = false;
// get path for backups
OUString aBakDir = SvtPathOptions().GetBackupPath();
if( !aBakDir.isEmpty() )
{
// create content for the parent folder ( = backup folder )
::ucbhelper::Content aContent;
Reference < css::ucb::XCommandEnvironment > xEnv;
if( ::utl::UCBContentHelper::ensureFolder(comphelper::getProcessComponentContext(), xEnv, aBakDir, aContent) )
{
// save as ".bak" file
INetURLObject aDest( aBakDir );
aDest.insertName( aSource.getName() );
aDest.setExtension( "bak" );
OUString aFileName = aDest.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
// create a content for the source file
::ucbhelper::Content aSourceContent;
if ( ::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aSourceContent ) )
{
try
{
// do the transfer ( copy source file to backup dir )
OUString sMimeType = pImpl->getFilterMimeType();
aContent.transferContent( aSourceContent,
::ucbhelper::InsertOperation::Copy,
aFileName,
NameClash::OVERWRITE,
sMimeType );
pImpl->m_aBackupURL = aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE );
pImpl->m_bRemoveBackup = false;
bSuccess = true;
}
catch ( const css::uno::Exception& )
{
}
}
}
}
if ( !bSuccess )
{
pImpl->m_eError = ERRCODE_SFX_CANTCREATEBACKUP;
}
}
void SfxMedium::ClearBackup_Impl()
{
if( pImpl->m_bRemoveBackup )
{
// currently a document is always stored in a new medium,
// thus if a backup can not be removed the backup URL should not be cleaned
if ( !pImpl->m_aBackupURL.isEmpty() )
{
if ( ::utl::UCBContentHelper::Kill( pImpl->m_aBackupURL ) )
{
pImpl->m_bRemoveBackup = false;
pImpl->m_aBackupURL.clear();
}
else
{
SAL_WARN( "sfx.doc", "Couldn't remove backup file!");
}
}
}
else
pImpl->m_aBackupURL.clear();
}
void SfxMedium::GetLockingStream_Impl()
{
if ( GetURLObject().GetProtocol() == INetProtocol::File
&& !pImpl->m_xLockingStream.is() )
{
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() )
{
// open the original document
uno::Sequence< beans::PropertyValue > xProps;
TransformItems( SID_OPENDOC, *GetItemSet(), xProps );
utl::MediaDescriptor aMedium( xProps );
aMedium.addInputStreamOwnLock();
uno::Reference< io::XInputStream > xInputStream;
aMedium[utl::MediaDescriptor::PROP_STREAM()] >>= pImpl->m_xLockingStream;
aMedium[utl::MediaDescriptor::PROP_INPUTSTREAM()] >>= xInputStream;
if ( !pImpl->pTempFile && pImpl->m_aName.isEmpty() )
{
// the medium is still based on the original file, it makes sense to initialize the streams
if ( pImpl->m_xLockingStream.is() )
pImpl->xStream = pImpl->m_xLockingStream;
if ( xInputStream.is() )
pImpl->xInputStream = xInputStream;
if ( !pImpl->xInputStream.is() && pImpl->xStream.is() )
pImpl->xInputStream = pImpl->xStream->getInputStream();
}
}
}
}
void SfxMedium::GetMedium_Impl()
{
if ( !pImpl->m_pInStream
|| (pImpl->bIsTemp && !pImpl->xInputStream.is() && !pImpl->m_xInputStreamToLoadFrom.is() && !pImpl->xStream.is() && !pImpl->m_xLockingStream.is() ) )
{
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, makeAny(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, makeAny( pImpl->xStream ) ) );
GetItemSet()->Put( SfxUnoAnyItem( SID_INPUTSTREAM, makeAny( pImpl->xInputStream ) ) );
}
}
//TODO/MBA: ErrorHandling - how to transport error from MediaDescriptor
if ( !GetError() && !pImpl->xStream.is() && !pImpl->xInputStream.is() )
SetError(ERRCODE_IO_ACCESSDENIED);
if ( !GetError() && !pImpl->m_pInStream )
{
if ( pImpl->xStream.is() )
pImpl->m_pInStream = utl::UcbStreamHelper::CreateStream( pImpl->xStream );
else if ( pImpl->xInputStream.is() )
pImpl->m_pInStream = utl::UcbStreamHelper::CreateStream( pImpl->xInputStream );
}
pImpl->bDownloadDone = true;
pImpl->aDoneLink.ClearPendingCall();
ErrCode nError = GetError();
pImpl->aDoneLink.Call( reinterpret_cast<void*>(sal_uInt32(nError)) );
}
}
bool SfxMedium::IsRemote() const
{
return pImpl->m_bRemote;
}
void SfxMedium::SetUpdatePickList(bool bVal)
{
pImpl->bUpdatePickList = bVal;
}
bool SfxMedium::IsUpdatePickList() const
{
return pImpl->bUpdatePickList;
}
void SfxMedium::SetLongName(const OUString &rName)
{
pImpl->m_aLongName = rName;
}
const OUString& SfxMedium::GetLongName() const
{
return pImpl->m_aLongName;
}
void SfxMedium::SetDoneLink( const Link<void*,void>& rLink )
{
pImpl->aDoneLink = rLink;
}
void SfxMedium::Download( const Link<void*,void>& aLink )
{
SetDoneLink( aLink );
GetInStream();
if ( pImpl->m_pInStream && !aLink.IsSet() )
{
while( !pImpl->bDownloadDone )
Application::Yield();
}
}
void SfxMedium::Init_Impl()
/* [Description]
Includes a valid:: sun:: com:: star:: util:: URL (If a file name was
previously in there) in the logical name and if available sets the
physical name as the file name.
*/
{
Reference< XOutputStream > rOutStream;
// TODO/LATER: handle lifetime of storages
pImpl->bDisposeStorage = false;
const SfxStringItem* pSalvageItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_DOC_SALVAGE, false);
if ( pSalvageItem && pSalvageItem->GetValue().isEmpty() )
{
pSalvageItem = nullptr;
pImpl->m_pSet->ClearItem( SID_DOC_SALVAGE );
}
if (!pImpl->m_aLogicName.isEmpty())
{
INetURLObject aUrl( pImpl->m_aLogicName );
INetProtocol eProt = aUrl.GetProtocol();
if ( eProt == INetProtocol::NotValid )
{
SAL_WARN( "sfx.doc", "URL <" << pImpl->m_aLogicName << "> with unknown protocol" );
}
else
{
if ( aUrl.HasMark() )
{
pImpl->m_aLogicName = aUrl.GetURLNoMark( INetURLObject::DecodeMechanism::NONE );
GetItemSet()->Put( SfxStringItem( SID_JUMPMARK, aUrl.GetMark() ) );
}
// try to convert the URL into a physical name - but never change a physical name
// physical name may be set if the logical name is changed after construction
if ( pImpl->m_aName.isEmpty() )
osl::FileBase::getSystemPathFromFileURL( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), pImpl->m_aName );
else
{
DBG_ASSERT( pSalvageItem, "Suspicious change of logical name!" );
}
}
}
if ( pSalvageItem && !pSalvageItem->GetValue().isEmpty() )
{
pImpl->m_aLogicName = pSalvageItem->GetValue();
pImpl->m_pURLObj.reset();
pImpl->m_bSalvageMode = true;
}
// in case output stream is by mistake here
// clear the reference
const SfxUnoAnyItem* pOutStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_OUTPUTSTREAM, false);
if( pOutStreamItem
&& ( !( pOutStreamItem->GetValue() >>= rOutStream )
|| !pImpl->m_aLogicName.startsWith("private:stream")) )
{
pImpl->m_pSet->ClearItem( SID_OUTPUTSTREAM );
SAL_WARN( "sfx.doc", "Unexpected Output stream parameter!" );
}
if (!pImpl->m_aLogicName.isEmpty())
{
// if the logic name is set it should be set in MediaDescriptor as well
const SfxStringItem* pFileNameItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_FILE_NAME, false);
if ( !pFileNameItem )
{
// let the ItemSet be created if necessary
GetItemSet()->Put(
SfxStringItem(
SID_FILE_NAME, INetURLObject( pImpl->m_aLogicName ).GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) );
}
}
SetIsRemote_Impl();
osl::DirectoryItem item;
if (osl::DirectoryItem::get(GetName(), item) == osl::FileBase::E_None) {
osl::FileStatus stat(osl_FileStatus_Mask_Attributes);
if (item.getFileStatus(stat) == osl::FileBase::E_None
&& stat.isValid(osl_FileStatus_Mask_Attributes))
{
if ((stat.getAttributes() & osl_File_Attribute_ReadOnly) != 0) {
pImpl->m_bOriginallyReadOnly = true;
}
}
}
}
SfxMedium::SfxMedium() : pImpl(new SfxMedium_Impl)
{
Init_Impl();
}
void SfxMedium::UseInteractionHandler( bool bUse )
{
pImpl->bAllowDefaultIntHdl = bUse;
}
css::uno::Reference< css::task::XInteractionHandler >
SfxMedium::GetInteractionHandler( bool bGetAlways )
{
// if interaction isn't allowed explicitly ... return empty reference!
if ( !bGetAlways && !pImpl->bUseInteractionHandler )
return css::uno::Reference< css::task::XInteractionHandler >();
// search a possible existing handler inside cached item set
if ( pImpl->m_pSet )
{
css::uno::Reference< css::task::XInteractionHandler > xHandler;
const SfxUnoAnyItem* pHandler = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_INTERACTIONHANDLER, false);
if ( pHandler && (pHandler->GetValue() >>= xHandler) && xHandler.is() )
return xHandler;
}
// if default interaction isn't allowed explicitly ... return empty reference!
if ( !bGetAlways && !pImpl->bAllowDefaultIntHdl )
return css::uno::Reference< css::task::XInteractionHandler >();
// otherwise return cached default handler ... if it exist.
if ( pImpl->xInteraction.is() )
return pImpl->xInteraction;
// create default handler and cache it!
Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
pImpl->xInteraction.set(
task::InteractionHandler::createWithParent(xContext, nullptr), UNO_QUERY_THROW );
return pImpl->xInteraction;
}
void SfxMedium::SetFilter( const std::shared_ptr<const SfxFilter>& pFilter )
{
pImpl->m_pFilter = pFilter;
}
const std::shared_ptr<const SfxFilter>& SfxMedium::GetFilter() const
{
return pImpl->m_pFilter;
}
sal_uInt32 SfxMedium::CreatePasswordToModifyHash( const OUString& aPasswd, bool bWriter )
{
sal_uInt32 nHash = 0;
if ( !aPasswd.isEmpty() )
{
if ( bWriter )
{
nHash = ::comphelper::DocPasswordHelper::GetWordHashAsUINT32( aPasswd );
}
else
{
rtl_TextEncoding nEncoding = osl_getThreadTextEncoding();
nHash = ::comphelper::DocPasswordHelper::GetXLHashAsUINT16( aPasswd, nEncoding );
}
}
return nHash;
}
void SfxMedium::Close()
{
if ( pImpl->xStorage.is() )
{
CloseStorage();
}
CloseStreams_Impl();
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() )
{
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& )
{
SAL_WARN( "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 )
{
::svt::DocumentLockFile aLockFile( pImpl->m_aLogicName );
try
{
pImpl->m_bLocked = false;
// TODO/LATER: A warning could be shown in case the file is not the own one
aLockFile.RemoveFile();
}
catch( const io::WrongFormatException& )
{
try
{
// erase the empty or corrupt file
aLockFile.RemoveFileDirectly();
}
catch( const uno::Exception& )
{}
}
catch( const uno::Exception& )
{}
}
#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 exsisting SvStream wrappers should be closed first
CloseStreams_Impl();
// in case of salvage mode the storage is based on the streams
if ( !pImpl->m_bSalvageMode )
{
try
{
if ( xInToClose.is() )
xInToClose->closeInput();
if ( xOutToClose.is() )
xOutToClose->closeOutput();
}
catch ( const uno::Exception& )
{
}
}
}
void SfxMedium::CloseStreams_Impl()
{
CloseInStream_Impl();
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;
pImpl->m_aLogicName = aNameP;
pImpl->m_pURLObj.reset();
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;
::utl::TempFile* pTmpFile = nullptr;
if ( pImpl->pTempFile )
{
pTmpFile = pImpl->pTempFile.release();
pImpl->m_aName.clear();
}
GetMedium_Impl();
if ( GetError() )
{
if ( pImpl->pTempFile )
{
pImpl->pTempFile->EnableKillingFile();
pImpl->pTempFile.reset();
}
pImpl->pTempFile.reset( pTmpFile );
if ( pImpl->pTempFile )
pImpl->m_aName = pImpl->pTempFile->GetFileName();
}
else if (pTmpFile)
{
pTmpFile->EnableKillingFile();
delete pTmpFile;
}
pImpl->bUseInteractionHandler = bUseInteractionHandler;
}
SfxMedium::SfxMedium(const OUString &rName, StreamMode nOpenMode, std::shared_ptr<const SfxFilter> pFilter, std::unique_ptr<SfxItemSet> pInSet) :
pImpl(new SfxMedium_Impl)
{
pImpl->m_pSet = std::move( 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, std::unique_ptr<SfxItemSet> pInSet) :
pImpl(new SfxMedium_Impl)
{
pImpl->m_pSet = std::move(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 );
OUString aFilterProvider, aFilterName;
{
const SfxPoolItem* pItem = nullptr;
if (pImpl->m_pSet->HasItem(SID_FILTER_PROVIDER, &pItem))
aFilterProvider = static_cast<const SfxStringItem*>(pItem)->GetValue();
if (pImpl->m_pSet->HasItem(SID_FILTER_NAME, &pItem))
aFilterName = static_cast<const SfxStringItem*>(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.reset(new 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 an 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();
}
SfxMedium::SfxMedium( const uno::Reference < embed::XStorage >& rStor, const OUString& rBaseURL, const 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 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 );
}
SfxMedium::~SfxMedium()
{
// if there is a requirement to clean the backup this is the last possibility to do it
ClearBackup_Impl();
Close();
if( pImpl->bIsTemp && !pImpl->m_aName.isEmpty() )
{
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
{
if (!pImpl->m_pURLObj)
{
pImpl->m_pURLObj.reset( new INetURLObject( pImpl->m_aLogicName ) );
pImpl->m_pURLObj->SetMark("");
}
return *pImpl->m_pURLObj;
}
void SfxMedium::SetExpired_Impl( const DateTime& rDateTime )
{
pImpl->aExpireTime = rDateTime;
}
bool SfxMedium::IsExpired() const
{
return pImpl->aExpireTime.IsValidAndGregorian() && pImpl->aExpireTime < DateTime( DateTime::SYSTEM );
}
SfxFrame* SfxMedium::GetLoadTargetFrame() const
{
return pImpl->wLoadTargetFrame;
}
void SfxMedium::setStreamToLoadFrom(const css::uno::Reference<css::io::XInputStream>& xInputStream, bool bIsReadOnly )
{
pImpl->m_xInputStreamToLoadFrom = xInputStream;
pImpl->m_bInputStreamIsReadOnly = bIsReadOnly;
}
void SfxMedium::SetLoadTargetFrame(SfxFrame* pFrame )
{
pImpl->wLoadTargetFrame = pFrame;
}
void SfxMedium::SetStorage_Impl( const uno::Reference < embed::XStorage >& rStor )
{
pImpl->xStorage = rStor;
}
SfxItemSet* SfxMedium::GetItemSet() const
{
// this method *must* return an ItemSet, returning NULL can cause crashes
if (!pImpl->m_pSet)
pImpl->m_pSet.reset( new SfxAllItemSet( SfxGetpApp()->GetPool() ) );
return pImpl->m_pSet.get();
}
SvKeyValueIterator* SfxMedium::GetHeaderAttributes_Impl()
{
if( !pImpl->xAttributes.is() )
{
pImpl->xAttributes = SvKeyValueIteratorRef( new SvKeyValueIterator );
if ( GetContent().is() )
{
try
{
Any aAny = pImpl->aContent.getPropertyValue("MediaType");
OUString aContentType;
aAny >>= aContentType;
pImpl->xAttributes->Append( SvKeyValue( "content-type", aContentType ) );
}
catch ( const css::uno::Exception& )
{
}
}
}
return pImpl->xAttributes.get();
}
css::uno::Reference< css::io::XInputStream > const & SfxMedium::GetInputStream()
{
if ( !pImpl->xInputStream.is() )
GetMedium_Impl();
return pImpl->xInputStream;
}
const uno::Sequence < util::RevisionTag >& SfxMedium::GetVersionList( bool _bNoReload )
{
// if the medium has no name, then this medium should represent a new document and can have no version info
if ( ( !_bNoReload || !pImpl->m_bVersionsAlreadyLoaded ) && !pImpl->aVersions.getLength() &&
( !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() )
{
// To determine a unique name for the stream
std::vector<sal_uInt32> aLongs;
sal_Int32 nLength = pImpl->aVersions.getLength();
for ( sal_Int32 m=0; m<nLength; m++ )
{
sal_uInt32 nVer = static_cast<sal_uInt32>( pImpl->aVersions[m].Identifier.copy(7).toInt32());
size_t n;
for ( n=0; n<aLongs.size(); ++n )
if ( nVer<aLongs[n] )
break;
aLongs.insert( aLongs.begin()+n, nVer );
}
std::vector<sal_uInt32>::size_type nKey;
for ( nKey=0; nKey<aLongs.size(); ++nKey )
if ( aLongs[nKey] > nKey+1 )
break;
OUString aRevName = "Version" + OUString::number( nKey + 1 );
pImpl->aVersions.realloc( nLength+1 );
rRevision.Identifier = aRevName;
pImpl->aVersions[nLength] = rRevision;
}
}
void SfxMedium::RemoveVersion_Impl( const OUString& rName )
{
if ( !pImpl->aVersions.getLength() )
return;
sal_Int32 nLength = pImpl->aVersions.getLength();
for ( sal_Int32 n=0; n<nLength; n++ )
{
if ( pImpl->aVersions[n].Identifier == rName )
{
for ( sal_Int32 m=n; m<nLength-1; m++ )
pImpl->aVersions[m] = pImpl->aVersions[m+1];
pImpl->aVersions.realloc(nLength-1);
return;
}
}
}
bool SfxMedium::TransferVersionList_Impl( SfxMedium const & rMedium )
{
if ( rMedium.pImpl->aVersions.getLength() )
{
pImpl->aVersions = rMedium.pImpl->aVersions;
return true;
}
return false;
}
void SfxMedium::SaveVersionList_Impl()
{
if ( GetStorage().is() )
{
if ( !pImpl->aVersions.getLength() )
return;
uno::Reference < document::XDocumentRevisionListPersistence > xWriter =
document::DocumentRevisionListPersistence::create( comphelper::getProcessComponentContext() );
try
{
xWriter->store( GetStorage(), pImpl->aVersions );
}
catch ( const uno::Exception& )
{
}
}
}
bool SfxMedium::IsReadOnly() const
{
// a) ReadOnly filter can't produce read/write contents!
bool bReadOnly = pImpl->m_pFilter && (pImpl->m_pFilter->GetFilterFlags() & SfxFilterFlags::OPENREADONLY);
// b) if filter allow read/write contents .. check open mode of the storage
if (!bReadOnly)
bReadOnly = !( GetOpenMode() & StreamMode::WRITE );
// c) the API can force the readonly state!
if (!bReadOnly)
{
const SfxBoolItem* pItem = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_DOC_READONLY, false);
if (pItem)
bReadOnly = pItem->GetValue();
}
return bReadOnly;
}
bool SfxMedium::IsOriginallyReadOnly() const
{
return pImpl->m_bOriginallyReadOnly;
}
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(std::unique_ptr<SfxMedium_Impl> const & pImpl)
{
OUString aLogicBase;
if (comphelper::isFileUrl(pImpl->m_aLogicName) && !pImpl->m_pInStream)
{
// Try to create the temp file in the same directory when storing.
sal_Int32 nOffset = pImpl->m_aLogicName.lastIndexOf("/");
if (nOffset != -1)
aLogicBase = pImpl->m_aLogicName.copy(0, nOffset);
if (aLogicBase == "file://")
// Doesn't make sense.
aLogicBase.clear();
}
if (pImpl->m_bHasEmbeddedObjects)
// Embedded objects would mean a special base, ignore that.
aLogicBase.clear();
return aLogicBase;
}
}
void SfxMedium::CreateTempFile( bool bReplace )
{
if ( pImpl->pTempFile )
{
if ( !bReplace )
return;
pImpl->pTempFile.reset();
pImpl->m_aName.clear();
}
OUString aLogicBase = GetLogicBase(pImpl);
pImpl->pTempFile.reset( new ::utl::TempFile(aLogicBase.isEmpty() ? nullptr : &aLogicBase) );
pImpl->pTempFile->EnableKillingFile();
pImpl->m_aName = pImpl->pTempFile->GetFileName();
OUString aTmpURL = pImpl->pTempFile->GetURL();
if ( pImpl->m_aName.isEmpty() || aTmpURL.isEmpty() )
{
SetError(ERRCODE_IO_CANTWRITE);
return;
}
if ( !(pImpl->m_nStorOpenMode & StreamMode::TRUNC) )
{
bool bTransferSuccess = false;
if ( GetContent().is()
&& GetURLObject().GetProtocol() == INetProtocol::File
&& ::utl::UCBContentHelper::IsDocument( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
{
// if there is already such a document, we should copy it
// if it is a file system use OS copy process
try
{
uno::Reference< css::ucb::XCommandEnvironment > xComEnv;
INetURLObject aTmpURLObj( aTmpURL );
OUString aFileName = aTmpURLObj.getName( INetURLObject::LAST_SEGMENT,
true,
INetURLObject::DecodeMechanism::WithCharset );
if ( !aFileName.isEmpty() && aTmpURLObj.removeSegment() )
{
::ucbhelper::Content aTargetContent( aTmpURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
OUString sMimeType = pImpl->getFilterMimeType();
aTargetContent.transferContent( pImpl->aContent, ::ucbhelper::InsertOperation::Copy, aFileName, NameClash::OVERWRITE, sMimeType );
SetWritableForUserOnly( aTmpURL );
bTransferSuccess = true;
}
}
catch( const uno::Exception& )
{}
if ( bTransferSuccess )
{
CloseOutStream();
CloseInStream();
}
}
if ( !bTransferSuccess && pImpl->m_pInStream )
{
// the case when there is no URL-access available or this is a remote protocol
// but there is an input stream
GetOutStream();
if ( pImpl->m_pOutStream )
{
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, 8192);
nErr = pImpl->m_pInStream->GetError();
pImpl->m_pOutStream->WriteBytes( pBuf, nRead );
}
bTransferSuccess = true;
delete[] pBuf;
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(pImpl);
pImpl->pTempFile.reset( new ::utl::TempFile(aLogicBase.isEmpty() ? nullptr : &aLogicBase) );
pImpl->pTempFile->EnableKillingFile();
pImpl->m_aName = pImpl->pTempFile->GetFileName();
if ( pImpl->m_aName.isEmpty() )
{
SetError(ERRCODE_IO_CANTWRITE);
return;
}
CloseOutStream_Impl();
CloseStorage();
}
bool SfxMedium::SignContents_Impl(bool bSignScriptingContent, bool bHasValidDocumentSignature,
const OUString& aSignatureLineId,
const Reference<XCertificate>& xCert,
const Reference<XGraphic>& xValidGraphic,
const Reference<XGraphic>& xInvalidGraphic,
const OUString& aComment)
{
bool bChanges = false;
if (IsOpen() || GetError())
{
SAL_WARN("sfx.doc", "The medium must be closed by the signer!");
return bChanges;
}
// The component should know if there was a valid document signature, since
// it should show a warning in this case
OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(GetStorage()));
uno::Reference< security::XDocumentDigitalSignatures > xSigner(
security::DocumentDigitalSignatures::createWithVersionAndValidSignature(
comphelper::getProcessComponentContext(), aODFVersion, bHasValidDocumentSignature ) );
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& rException)
{
if (bODF)
SAL_WARN("sfx.doc", "ODF stream is not a zip storage: " << rException);
}
if ( !xWriteableZipStor.is() && bODF )
throw uno::RuntimeException();
uno::Reference< embed::XStorage > xMetaInf;
uno::Reference<container::XNameAccess> xNameAccess(xWriteableZipStor, uno::UNO_QUERY);
if (xNameAccess.is() && xNameAccess->hasByName("META-INF"))
{
xMetaInf = xWriteableZipStor->openStorageElement(
"META-INF",
embed::ElementModes::READWRITE );
if ( !xMetaInf.is() )
throw uno::RuntimeException();
}
if ( bSignScriptingContent )
{
// If the signature has already the document signature it will be removed
// after the scripting signature is inserted.
uno::Reference< io::XStream > xStream(
xMetaInf->openStreamElement( xSigner->getScriptingContentSignatureDefaultStreamName(),
embed::ElementModes::READWRITE ),
uno::UNO_SET_THROW );
if ( xSigner->signScriptingContent( GetZipStorageToSign_Impl(), xStream ) )
{
// remove the document signature if any
OUString aDocSigName = xSigner->getDocumentContentSignatureDefaultStreamName();
if ( !aDocSigName.isEmpty() && xMetaInf->hasByName( aDocSigName ) )
xMetaInf->removeElement( aDocSigName );
uno::Reference< embed::XTransactedObject > xTransact( xMetaInf, uno::UNO_QUERY_THROW );
xTransact->commit();
xTransact.set( xWriteableZipStor, uno::UNO_QUERY_THROW );
xTransact->commit();
// the temporary file has been written, commit it to the original file
Commit();
bChanges = true;
}
}
else
{
if (xMetaInf.is())
{
// ODF.
uno::Reference< io::XStream > xStream;
if (GetFilter() && GetFilter()->IsOwnFormat())
xStream.set(xMetaInf->openStreamElement(xSigner->getDocumentContentSignatureDefaultStreamName(), embed::ElementModes::READWRITE), uno::UNO_SET_THROW);
bool bSuccess = false;
if (xCert.is())
bSuccess = xSigner->signSignatureLine(
GetZipStorageToSign_Impl(), xStream, aSignatureLineId, xCert,
xValidGraphic, xInvalidGraphic, aComment);
else
bSuccess = xSigner->signDocumentContent(GetZipStorageToSign_Impl(),
xStream);
if (bSuccess)
{
uno::Reference< embed::XTransactedObject > xTransact( xMetaInf, uno::UNO_QUERY_THROW );
xTransact->commit();
xTransact.set( xWriteableZipStor, uno::UNO_QUERY_THROW );
xTransact->commit();
// the temporary file has been written, commit it to the original file
Commit();
bChanges = true;
}
}
else if (xWriteableZipStor.is())
{
// OOXML.
uno::Reference<io::XStream> xStream;
bool bSuccess = false;
if (xCert.is())
{
bSuccess = xSigner->signSignatureLine(
GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream, aSignatureLineId,
xCert, xValidGraphic, xInvalidGraphic, aComment);
}
else
{
// We need read-write to be able to add the signature relation.
bSuccess =xSigner->signDocumentContent(
GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream);
}
if (bSuccess)
{
uno::Reference<embed::XTransactedObject> xTransact(xWriteableZipStor, uno::UNO_QUERY_THROW);
xTransact->commit();
// the temporary file has been written, commit it to the original file
Commit();
bChanges = true;
}
}
else
{
// Something not ZIP based: e.g. PDF.
std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(GetName(), StreamMode::READ | StreamMode::WRITE));
uno::Reference<io::XStream> xStream(new utl::OStreamWrapper(*pStream));
if (xSigner->signDocumentContent(uno::Reference<embed::XStorage>(), xStream))
bChanges = true;
}
}
}
catch ( const uno::Exception& )
{
SAL_WARN( "sfx.doc", "Couldn't use signing functionality!" );
}
CloseAndRelease();
ResetError();
return bChanges;
}
SignatureState SfxMedium::GetCachedSignatureState_Impl()
{
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( const OUString& aURL )
{
OUString aResult;
if ( !aURL.isEmpty() )
{
sal_Int32 nPrefixLen = aURL.lastIndexOf( '.' );
OUString aExt = ( nPrefixLen == -1 ) ? OUString() : aURL.copy( nPrefixLen );
OUString aNewTempFileURL = ::utl::TempFile( OUString(), true, &aExt ).GetURL();
if ( !aNewTempFileURL.isEmpty() )
{
INetURLObject aSource( aURL );
INetURLObject aDest( aNewTempFileURL );
OUString aFileName = aDest.getName( INetURLObject::LAST_SEGMENT,
true,
INetURLObject::DecodeMechanism::WithCharset );
if ( !aFileName.isEmpty() && aDest.removeSegment() )
{
try
{
uno::Reference< css::ucb::XCommandEnvironment > xComEnv;
::ucbhelper::Content aTargetContent( aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
::ucbhelper::Content aSourceContent( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
aTargetContent.transferContent( aSourceContent,
::ucbhelper::InsertOperation::Copy,
aFileName,
NameClash::OVERWRITE );
aResult = aNewTempFileURL;
}
catch( const uno::Exception& )
{}
}
}
}
return aResult;
}
bool SfxMedium::CallApproveHandler(const uno::Reference< task::XInteractionHandler >& xHandler, const uno::Any& rRequest, bool bAllowAbort)
{
bool bResult = false;
if ( xHandler.is() )
{
try
{
uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations( bAllowAbort ? 2 : 1 );
::rtl::Reference< ::comphelper::OInteractionApprove > pApprove( new ::comphelper::OInteractionApprove );
aContinuations[ 0 ] = pApprove.get();
if ( bAllowAbort )
{
::rtl::Reference< ::comphelper::OInteractionAbort > pAbort( new ::comphelper::OInteractionAbort );
aContinuations[ 1 ] = pAbort.get();
}
xHandler->handle(::framework::InteractionRequest::CreateRequest(rRequest, aContinuations));
bResult = pApprove->wasSelected();
}
catch( const Exception& )
{
}
}
return bResult;
}
OUString SfxMedium::SwitchDocumentToTempFile()
{
// the method returns empty string in case of failure
OUString aResult;
OUString aOrigURL = pImpl->m_aLogicName;
if ( !aOrigURL.isEmpty() )
{
sal_Int32 nPrefixLen = aOrigURL.lastIndexOf( '.' );
OUString const aExt = (nPrefixLen == -1)
? OUString()
: aOrigURL.copy(nPrefixLen);
OUString aNewURL = ::utl::TempFile( OUString(), true, &aExt ).GetURL();
// TODO/LATER: In future the aLogicName should be set to shared folder URL
// and a temporary file should be created. Transport_Impl should be impossible then.
if ( !aNewURL.isEmpty() )
{
uno::Reference< embed::XStorage > xStorage = GetStorage();
uno::Reference< embed::XOptimizedStorage > xOptStorage( xStorage, uno::UNO_QUERY );
if ( xOptStorage.is() )
{
// TODO/LATER: reuse the pImpl->pTempFile if it already exists
CanDisposeStorage_Impl( false );
Close();
SetPhysicalName_Impl( OUString() );
SetName( aNewURL );
// remove the readonly state
bool bWasReadonly = false;
pImpl->m_nStorOpenMode = SFX_STREAM_READWRITE;
const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
if ( pReadOnlyItem && pReadOnlyItem->GetValue() )
bWasReadonly = true;
GetItemSet()->ClearItem( SID_DOC_READONLY );
GetMedium_Impl();
LockOrigFileOnDemand( false, false );
CreateTempFile();
GetMedium_Impl();
if ( pImpl->xStream.is() )
{
try
{
xOptStorage->writeAndAttachToStream( pImpl->xStream );
pImpl->xStorage = xStorage;
aResult = aNewURL;
}
catch( const uno::Exception& )
{}
}
if ( aResult.isEmpty() )
{
Close();
SetPhysicalName_Impl( OUString() );
SetName( aOrigURL );
if ( bWasReadonly )
{
// set the readonly state back
pImpl->m_nStorOpenMode = SFX_STREAM_READONLY;
GetItemSet()->Put( SfxBoolItem(SID_DOC_READONLY, true));
}
GetMedium_Impl();
pImpl->xStorage = xStorage;
}
}
}
}
return aResult;
}
bool SfxMedium::SwitchDocumentToFile( const OUString& aURL )
{
// the method is only for storage based documents
bool bResult = false;
OUString aOrigURL = pImpl->m_aLogicName;
if ( !aURL.isEmpty() && !aOrigURL.isEmpty() )
{
uno::Reference< embed::XStorage > xStorage = GetStorage();
uno::Reference< embed::XOptimizedStorage > xOptStorage( xStorage, uno::UNO_QUERY );
// TODO/LATER: reuse the pImpl->pTempFile if it already exists
CanDisposeStorage_Impl( false );
Close();
SetPhysicalName_Impl( OUString() );
SetName( aURL );
// open the temporary file based document
GetMedium_Impl();
LockOrigFileOnDemand( false, false );
CreateTempFile();
GetMedium_Impl();
if ( pImpl->xStream.is() )
{
try
{
uno::Reference< io::XTruncate > xTruncate( pImpl->xStream, uno::UNO_QUERY_THROW );
xTruncate->truncate();
if ( xOptStorage.is() )
xOptStorage->writeAndAttachToStream( pImpl->xStream );
pImpl->xStorage = xStorage;
bResult = true;
}
catch( const uno::Exception& )
{}
}
if ( !bResult )
{
Close();
SetPhysicalName_Impl( OUString() );
SetName( aOrigURL );
GetMedium_Impl();
pImpl->xStorage = xStorage;
}
}
return bResult;
}
void SfxMedium::SetInCheckIn( bool bInCheckIn )
{
pImpl->m_bInCheckIn = bInCheckIn;
}
bool SfxMedium::IsInCheckIn( )
{
return pImpl->m_bInCheckIn;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V1019 Compound assignment expression is used inside condition.
↑ V547 Expression 'pVersion' is always true.
↑ V1019 Compound assignment expression is used inside condition.
↑ V519 The 'aAny' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 2149, 2150.
↑ V1019 Compound assignment expression is used inside condition.
↑ V1019 Compound assignment expression 'pHandler->GetValue() >>= xHandler' is used inside condition.