/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
/// Gets default attributes of a file:// URL.
sal_uInt64 GetDefaultFileAttributes(const OUString& rURL)
{
sal_uInt64 nRet = 0;
if (!comphelper::isFileUrl(rURL)) return nRet;
// Make sure the file exists (and create it if not).
osl::File aFile(rURL);
osl::File::RC nRes = aFile.open(osl_File_OpenFlag_Create); if (nRes != osl::File::E_None && nRes != osl::File::E_EXIST) return nRet;
aFile.close();
osl::DirectoryItem aItem; if (osl::DirectoryItem::get(rURL, aItem) != osl::DirectoryItem::E_None) return nRet;
osl::FileStatus aStatus(osl_FileStatus_Mask_Attributes); if (aItem.getFileStatus(aStatus) != osl::DirectoryItem::E_None) return nRet;
nRet = aStatus.getAttributes(); return nRet;
}
/// Determines if rURL is safe to move or not. bool IsFileMovable(const INetURLObject& rURL)
{ #ifdef MACOSX
(void)rURL; // Hide extension macOS-specific file property would be lost. returnfalse; #else
if (rURL.GetProtocol() != INetProtocol::File) // Not a file:// URL. returnfalse;
#ifdef UNX
OUString sPath = rURL.getFSysPath(FSysStyle::Unix); if (sPath.isEmpty()) returnfalse;
struct stat buf; if (lstat(sPath.toUtf8().getStr(), &buf) != 0) returnfalse;
// Hardlink or symlink: osl::File::move() doesn't play with these nicely. if (buf.st_nlink > 1 || S_ISLNK(buf.st_mode)) returnfalse;
// Read-only target path: this would be silently replaced. if (access(sPath.toUtf8().getStr(), W_OK) == -1) returnfalse;
#elifdefined _WIN32 if (tools::IsMappedWebDAVPath(rURL.GetMainURL(INetURLObject::DecodeMechanism::NONE))) returnfalse; #endif
returntrue; #endif
}
class CheckReadOnlyTaskTerminateListener
: public ::cppu::WeakImplHelper<css::frame::XTerminateListener>
{ public: // XEventListener void SAL_CALL disposing(const css::lang::EventObject& Source) override;
// 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;
// tdf#95144 add a default css::ucb::XCommandEnvironment // in order to have the WebDAV UCP provider manage https protocol certificates correctly
css:: uno::Reference< task::XInteractionHandler > xIH(
css::task::InteractionHandler::createWithParent( comphelper::getProcessComponentContext(), nullptr ) );
css::uno::Reference< css::ucb::XProgressHandler > xProgress;
rtl::Reference<::ucbhelper::CommandEnvironment> pCommandEnv = new ::ucbhelper::CommandEnvironment( new comphelper::SimpleFileAccessInteraction( xIH ), xProgress );
void SfxMedium::CloseInStream_Impl(bool bInDestruction)
{ // if there is a storage based on the InStream, we have to // close the storage, too, because otherwise the storage // would use an invalid ( deleted ) stream. if ( pImpl->m_pInStream && pImpl->xStorage.is() )
{ if ( pImpl->bStorageBasedOnInStream )
CloseStorage();
}
if ( !pImpl->m_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();
}
}
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 );
}
}
if ( GetErrorIgnoreWarning() == ERRCODE_NONE )
{ // does something only in case there is a temporary file ( means aName points to different location than aLogicName )
Transfer_Impl();
}
// if the medium was constructed with a Storage: use this one, not a temp. storage // if a temporary storage already exists: use it if (pImpl->xStorage.is()
&& (pImpl->m_bODFWholesomeEncryption || pImpl->m_aLogicName.isEmpty() || pImpl->pTempFile))
{ return pImpl->xStorage;
}
// if necessary close stream that was used for reading if ( pImpl->m_pInStream && !pImpl->m_pInStream->IsWritable() )
CloseInStream();
DBG_ASSERT( !pImpl->m_pOutStream, "OutStream in a readonly Medium?!" );
// TODO/LATER: The current solution is to store the document temporary and then copy it to the target location; // in future it should be stored directly and then copied to the temporary location, since in this case no // file attributes have to be preserved and system copying mechanics could be used instead of streaming.
CreateTempFileNoCopy();
return GetStorage();
}
bool SfxMedium::SetEncryptionDataToStorage_Impl()
{ // in case media-descriptor contains password it should be used on opening if ( !pImpl->xStorage.is() || !pImpl->m_pSet ) returnfalse;
// replace the password with encryption data
pImpl->m_pSet->ClearItem( SID_PASSWORD );
pImpl->m_pSet->Put( SfxUnoAnyItem( SID_ENCRYPTIONDATA, uno::Any( aEncryptionData ) ) );
try
{
::comphelper::OStorageHelper::SetCommonStorageEncryptionData( pImpl->xStorage, aEncryptionData );
} catch( const uno::Exception& )
{
SAL_WARN( "sfx.doc", "It must be possible to set a common password for the storage" );
SetError(ERRCODE_IO_GENERAL); returnfalse;
} returntrue;
}
#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.
if (!sUserData.isEmpty())
sUserData += " (MS Office)"; // Mention the used office suite
return sUserData;
}
OUString tryForeignLockfiles(std::u16string_view sDocURL)
{
OUString sUserData = tryMSOwnerFiles(sDocURL); // here we can test for empty result, and add other known applications' lockfile testing return sUserData.trim();
}
}
// 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 ( bOwnLock )
{
aInfo = aData[LockFileComponent::EDITTIME];
xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::Any(
document::OwnLockOnDocumentRequest( OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo, !bIsLoading ) ) );
} else
{ // Use a fourth continuation in case there's no filesystem lock: // "Ignore lock file and open/replace the document" if (!bHandleSysLocked)
nContinuations = 4;
if (aInfo.isEmpty() && !GetURLObject().isAnyKnownWebDAVScheme()) // Try to get name of user who has locked the file using other applications
aInfo = tryForeignLockfiles(
GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::NONE));
if (!bIsLoading) // so, !bHandleSysLocked
{
xInteractionRequestImpl = new ::ucbhelper::InteractionRequest(uno::Any(
document::LockedOnSavingRequest(OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo))); // Currently, only the last "Retry" continuation (meaning ignore the lock and try overwriting) can be returned.
} else/*logically therefore bIsLoading is set */
{
xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::Any(
document::LockedDocumentRequest( OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo ) ) );
}
}
uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations(nContinuations); auto pContinuations = aContinuations.getArray();
pContinuations[0] = new ::ucbhelper::InteractionAbort( xInteractionRequestImpl.get() );
pContinuations[1] = new ::ucbhelper::InteractionApprove( xInteractionRequestImpl.get() );
pContinuations[2] = new ::ucbhelper::InteractionDisapprove( xInteractionRequestImpl.get() ); if (nContinuations > 3)
{ // We use InteractionRetry to reflect that user wants to // ignore the (stale?) alien lock file and open/overwrite the document
pContinuations[3] = new ::ucbhelper::InteractionRetry(xInteractionRequestImpl.get());
}
xInteractionRequestImpl->setContinuations( aContinuations );
xHandler->handle( xInteractionRequestImpl );
bool bOpenReadOnly = false;
::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = xInteractionRequestImpl->getSelection(); if ( uno::Reference< task::XInteractionAbort >( cppu::getXWeak(xSelected.get()), uno::UNO_QUERY ).is() )
{
SetError(ERRCODE_ABORT);
} elseif ( uno::Reference< task::XInteractionDisapprove >( cppu::getXWeak(xSelected.get()), uno::UNO_QUERY ).is() )
{ // own lock on loading, user has selected to ignore the lock // own lock on saving, user has selected to ignore the lock // alien lock on loading, user has selected to edit a copy of document // TODO/LATER: alien lock on saving, user has selected to do SaveAs to different location if ( !bOwnLock ) // bIsLoading implied from outermost condition
{ // means that a copy of the document should be opened
GetItemSet().Put( SfxBoolItem( SID_TEMPLATE, true ) );
} else
nResult = ShowLockResult::Succeeded;
} elseif (uno::Reference< task::XInteractionRetry >(cppu::getXWeak(xSelected.get()), uno::UNO_QUERY).is())
{ // User decided to ignore the alien (stale?) lock file without filesystem lock
nResult = ShowLockResult::Succeeded;
} elseif (uno::Reference< task::XInteractionApprove >( cppu::getXWeak(xSelected.get()), uno::UNO_QUERY ).is())
{
bOpenReadOnly = true;
} else// user selected "Notify"
{
pImpl->m_bNotifyWhenEditable = true;
AddToCheckEditableWorkerList();
bOpenReadOnly = true;
}
if (bOpenReadOnly)
{ // own lock on loading, user has selected to open readonly // own lock on saving, user has selected to open readonly // alien lock on loading, user has selected to retry saving // TODO/LATER: alien lock on saving, user has selected to retry saving
if (bIsLoading)
GetItemSet().Put(SfxBoolItem(SID_DOC_READONLY, true)); else
nResult = ShowLockResult::Try;
}
} else
{ if ( bIsLoading )
{ // if no interaction handler is provided the default answer is open readonly // that usually happens in case the document is loaded per API // so the document must be opened readonly for backward compatibility
GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
} else
SetError(ERRCODE_IO_ACCESSDENIED);
}
return nResult;
}
bool SfxMedium::ShowLockFileProblemDialog(MessageDlg nWhichDlg)
{ // system file locking is not active, ask user whether he wants to open the document without any locking
uno::Reference< task::XInteractionHandler > xHandler = GetInteractionHandler();
if (xHandler.is())
{
::rtl::Reference< ::ucbhelper::InteractionRequest > xIgnoreRequestImpl;
switch (nWhichDlg)
{ case MessageDlg::LockFileIgnore:
xIgnoreRequestImpl = new ::ucbhelper::InteractionRequest(uno::Any( document::LockFileIgnoreRequest() )); break; case MessageDlg::LockFileCorrupt:
xIgnoreRequestImpl = new ::ucbhelper::InteractionRequest(uno::Any( document::LockFileCorruptRequest() )); break;
}
uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations{ new ::ucbhelper::InteractionAbort(xIgnoreRequestImpl.get()), new ::ucbhelper::InteractionApprove(xIgnoreRequestImpl.get())
};
xIgnoreRequestImpl->setContinuations(aContinuations);
// for LOCK request, suppress dialog on 403, typically indicates read-only // document and there's a 2nd dialog prompting to open a copy anyway class LockInteractionHandler : public ::cppu::WeakImplHelper<task::XInteractionHandler>
{ private:
uno::Reference<task::XInteractionHandler> m_xHandler;
virtualvoid SAL_CALL handle(uno::Reference<task::XInteractionRequest> const& xRequest) override
{
ucb::InteractiveNetworkWriteException readException;
ucb::InteractiveNetworkReadException writeException; if ((xRequest->getRequest() >>= readException)
|| (xRequest->getRequest() >>= writeException))
{ return; // 403 gets reported as one of these; ignore to avoid dialog
}
m_xHandler->handle(xRequest);
}
};
} // namespace
#endif// HAVE_FEATURE_MULTIUSER_ENVIRONMENT
// sets SID_DOC_READONLY if the document cannot be opened for editing // if user cancel the loading the ERROR_ABORT is set
SfxMedium::LockFileResult SfxMedium::LockOrigFileOnDemand(bool bLoading, bool bNoUI, bool bTryIgnoreLockFile,
LockFileEntry* pLockData)
{ #if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT
(void) bLoading;
(void) bNoUI;
(void) bTryIgnoreLockFile;
(void) pLockData; return LockFileResult::Succeeded; #else
LockFileResult eResult = LockFileResult::Failed;
// check if path scheme is http:// or https:// // may be this is better if used always, in Android and iOS as well? // if this code should be always there, remember to move the relevant code in UnlockFile method as well !
if ( GetURLObject().isAnyKnownWebDAVScheme() )
{ // do nothing if WebDAV locking is disabled if (!IsWebDAVLockingUsed()) return LockFileResult::Succeeded;
{ bool bResult = pImpl->m_bLocked; bool bIsTemplate = false; // so, this is webdav stuff... if ( !bResult )
{ // no read-write access is necessary on loading if the document is explicitly opened as copy const SfxBoolItem* pTemplateItem = GetItemSet().GetItem(SID_TEMPLATE, false);
bIsTemplate = ( bLoading && pTemplateItem && pTemplateItem->GetValue() );
}
if ( !bIsTemplate && !bResult && !IsReadOnly() )
{
ShowLockResult bUIStatus = ShowLockResult::NoLock; do
{ if( !bResult )
{
uno::Reference< task::XInteractionHandler > xCHandler = GetInteractionHandler( true ); // Dialog with error is superfluous: // on loading, will result in read-only with infobar. // bNoUI case for Reload failing, will open dialog later. if (bLoading || bNoUI)
{
xCHandler = new LockInteractionHandler(xCHandler);
}
Reference< css::ucb::XCommandEnvironment > xComEnv = new ::ucbhelper::CommandEnvironment(
xCHandler, Reference< css::ucb::XProgressHandler >() );
try
{
aContentToLock.lock();
bResult = true;
} catch ( ucb::InteractiveLockingLockedException& )
{ // received when the resource is already locked if (!bNoUI || pLockData)
{ // get the lock owner, using a special ucb.webdav property // the owner property retrieved here is what the other principal send the server // when activating the lock. // See http://tools.ietf.org/html/rfc4918#section-14.17 for details
LockFileEntry aLockData;
aLockData[LockFileComponent::OOOUSERNAME] = "Unknown user"; // This solution works right when the LO user name and the WebDAV user // name are the same. // A better thing to do would be to obtain the 'real' WebDAV user name, // but that's not possible from a WebDAV UCP provider client.
LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry(); // use the current LO user name as the system name
aLockData[LockFileComponent::SYSUSERNAME]
= aOwnData[LockFileComponent::SYSUSERNAME];
uno::Sequence<css::ucb::Lock> aLocks; // getting the property, send a PROPFIND to the server over the net if ((aContentToLock.getPropertyValue(u"DAV:lockdiscovery"_ustr) >>= aLocks) && aLocks.hasElements())
{ // got at least a lock, show the owner of the first lock returned const css::ucb::Lock& aLock = aLocks[0];
OUString aOwner; if (aLock.Owner >>= aOwner)
{ // we need to display the WebDAV user name owning the lock, not the local one
aLockData[LockFileComponent::OOOUSERNAME] = aOwner;
}
}
if (!bNoUI)
{
bUIStatus = ShowLockedDocumentDialog(aLockData, bLoading, false, true);
}
if (pLockData)
{
std::copy(aLockData.begin(), aLockData.end(), pLockData->begin());
}
}
} catch( ucb::InteractiveNetworkWriteException& )
{ // This catch it's not really needed, here just for the sake of documentation on the behaviour. // This is the most likely reason: // - the remote site is a WebDAV with special configuration: read/only for read operations // and read/write for write operations, the user is not allowed to lock/write and // she cancelled the credentials request. // this is not actually an error, but the exception is sent directly from ucb, avoiding the automatic // management that takes part in cancelCommandExecution() // Unfortunately there is no InteractiveNetwork*Exception available to signal this more correctly // since it mostly happens on read/only part of webdav, this can be the most correct // exception available
} catch( uno::Exception& )
{
TOOLS_WARN_EXCEPTION( "sfx.doc", "Locking exception: WebDAV while trying to lock the file");
}
}
} while( !bResult && bUIStatus == ShowLockResult::Try );
}
pImpl->m_bLocked = bResult;
if ( !bResult && GetErrorIgnoreWarning() == ERRCODE_NONE )
{ // the error should be set in case it is storing process // or the document has been opened for editing explicitly const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
if (!IsLockingUsed()) return LockFileResult::Succeeded; if (GetURLObject().HasError()) return eResult;
try
{ if ( pImpl->m_bLocked && bLoading
&& GetURLObject().GetProtocol() == INetProtocol::File )
{ // if the document is already locked the system locking might be temporarily off after storing // check whether the system file locking should be taken again
GetLockingStream_Impl();
}
bool bResult = pImpl->m_bLocked;
if ( !bResult )
{ // no read-write access is necessary on loading if the document is explicitly opened as copy const SfxBoolItem* pTemplateItem = GetItemSet().GetItem(SID_TEMPLATE, false);
bResult = ( bLoading && pTemplateItem && pTemplateItem->GetValue() );
}
if ( !bResult && !IsReadOnly() )
{ bool bContentReadonly = false; if ( bLoading && GetURLObject().GetProtocol() == INetProtocol::File )
{ // let the original document be opened to check the possibility to open it for editing // and to let the writable stream stay open to hold the lock on the document
GetLockingStream_Impl();
}
// "IsReadOnly" property does not allow to detect whether the file is readonly always // so we try always to open the file for editing // the file is readonly only in case the read-write stream can not be opened if ( bLoading && !pImpl->m_xLockingStream.is() )
{ try
{ // MediaDescriptor does this check also, the duplication should be avoided in future
Reference< css::ucb::XCommandEnvironment > xDummyEnv;
::ucbhelper::Content aContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext() );
aContent.getPropertyValue(u"IsReadOnly"_ustr) >>= bContentReadonly;
} catch( const uno::Exception& ) {}
}
// do further checks only if the file not readonly in fs if ( !bContentReadonly )
{ // the special file locking should be used only for suitable URLs if ( isSuitableProtocolForLocking( pImpl->m_aLogicName ) )
{
// in case of storing the document should request the output before locking if ( bLoading )
{ // let the stream be opened to check the system file locking
GetMedium_Impl(); if (GetErrorIgnoreWarning() != ERRCODE_NONE) { return eResult;
}
}
// check whether system file locking has been used, the default value is false bool bUseSystemLock = comphelper::isFileUrl( pImpl->m_aLogicName ) && IsSystemFileLockingUsed();
// TODO/LATER: This implementation does not allow to detect the system lock on saving here, actually this is no big problem // if system lock is used the writeable stream should be available bool bHandleSysLocked = ( bLoading && bUseSystemLock && !pImpl->xStream.is() && !pImpl->m_pOutStream );
// The file is attempted to get locked for the duration of lockfile creation on save
std::unique_ptr<osl::File> pFileLock; if (!bLoading && bUseSystemLock && pImpl->pTempFile)
{
INetURLObject aDest(GetURLObject());
OUString aDestURL(aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE));
if (comphelper::isFileUrl(aDestURL) || !aDest.removeSegment())
{
pFileLock = std::make_unique<osl::File>(aDestURL); auto rc = pFileLock->open(osl_File_OpenFlag_Write); if (rc == osl::FileBase::E_ACCES)
bHandleSysLocked = true;
}
}
do
{ try
{
::svt::DocumentLockFile aLockFile( pImpl->m_aLogicName );
if (!bHandleSysLocked)
{ try
{
bResult = aLockFile.CreateOwnLockFile(); if(pMSOLockFile)
bResult &= pMSOLockFile->CreateOwnLockFile();
} catch (const uno::Exception&)
{ if (tools::IsMappedWebDAVPath(GetURLObject().GetMainURL(
INetURLObject::DecodeMechanism::NONE)))
{ // This is a path that redirects to a WebDAV resource; // so failure creating lockfile is not an error here.
bResult = true;
} elseif (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;
}
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;
}
}
pImpl->m_bLocked = bResult;
} else
{ // this is no file URL, check whether the file is readonly
bResult = !bContentReadonly;
}
} else// read-only
{
AddToCheckEditableWorkerList();
}
}
if ( !bResult && GetErrorIgnoreWarning() == ERRCODE_NONE )
{ // the error should be set in case it is storing process // or the document has been opened for editing explicitly const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
// when the file is locked, get the current file date if ( bResult && DocNeedsFileDateCheck() )
GetInitFileDate( true );
if ( bResult )
eResult = LockFileResult::Succeeded;
} catch( const uno::Exception& )
{
TOOLS_WARN_EXCEPTION( "sfx.doc", "Locking exception: high probability, that the content has not been created" );
}
return eResult; #endif
}
// this either returns non-null or throws exception
uno::Reference<embed::XStorage>
SfxMedium::TryEncryptedInnerPackage(uno::Reference<embed::XStorage> const & xStorage)
{
uno::Reference<embed::XStorage> xRet; if (xStorage->hasByName(u"encrypted-package"_ustr))
{
uno::Reference<io::XStream> const
xDecryptedInnerPackage = xStorage->openStreamElement(
u"encrypted-package"_ustr,
embed::ElementModes::READ | embed::ElementModes::NOCREATE); // either this throws due to wrong password or IO error, or returns stream
assert(xDecryptedInnerPackage.is()); // need a seekable stream => copy
Reference<uno::XComponentContext> const& xContext(::comphelper::getProcessComponentContext());
uno::Reference<io::XStream> const xDecryptedInnerPackageStream(
xContext->getServiceManager()->createInstanceWithContext(
u"com.sun.star.comp.MemoryStream"_ustr, xContext),
UNO_QUERY_THROW);
comphelper::OStorageHelper::CopyInputToOutput(xDecryptedInnerPackage->getInputStream(), xDecryptedInnerPackageStream->getOutputStream());
xDecryptedInnerPackageStream->getOutputStream()->closeOutput(); #if 0 // debug: dump to temp file
uno::Reference<io::XTempFile> const xTempFile(io::TempFile::create(xContext), uno::UNO_SET_THROW);
xTempFile->setRemoveFile(false);
comphelper::OStorageHelper::CopyInputToOutput(xDecryptedInnerPackageStream->getInputStream(), xTempFile->getOutputStream());
xTempFile->getOutputStream()->closeOutput();
SAL_DE BUG("AAA tempfile " << xTempFile->getResourceName());
uno::Reference<io::XSeekable>(xDecryptedInnerPackageStream, uno::UNO_QUERY_THROW)->seek(0); #endif // create inner storage; opening the stream should have already verified // the password so any failure here is probably due to a bug
xRet = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
PACKAGE_STORAGE_FORMAT_STRING, xDecryptedInnerPackageStream,
embed::ElementModes::READWRITE, xContext, false);
assert(xRet.is()); // consistency check: outer and inner package must have same mimetype
OUString const outerMediaType(uno::Reference<beans::XPropertySet>(pImpl->xStorage,
uno::UNO_QUERY_THROW)->getPropertyValue(u"MediaType"_ustr).get<OUString>());
OUString const innerMediaType(uno::Reference<beans::XPropertySet>(xRet,
uno::UNO_QUERY_THROW)->getPropertyValue(u"MediaType"_ustr).get<OUString>()); if (outerMediaType.isEmpty() || outerMediaType != innerMediaType)
{ throw io::WrongFormatException(u"MediaType inconsistent in encrypted ODF package"_ustr);
} // success:
pImpl->m_bODFWholesomeEncryption = true;
pImpl->m_xODFDecryptedInnerPackageStream = xDecryptedInnerPackageStream;
pImpl->m_xODFEncryptedOuterStorage = xStorage;
pImpl->xStorage = xRet;
} return xRet;
}
// the first arguments will be filled later
aArgs.realloc( 3 );
pArgs = aArgs.getArray();
pArgs[2] <<= aAddProps;
}
if ( pImpl->xStream.is() )
{ // since the storage is based on temporary stream we open it always read-write
pArgs[0] <<= pImpl->xStream;
pArgs[1] <<= embed::ElementModes::READWRITE;
pImpl->bStorageBasedOnInStream = true; if (pImpl->m_bDisableFileSync)
{ // Forward NoFileSync to the storage factory.
aArgs.realloc(3); // ??? this may re-write the data added above for pRepairItem
pArgs = aArgs.getArray();
uno::Sequence<beans::PropertyValue> aProperties(
comphelper::InitPropertySequence({ { "NoFileSync", uno::Any(true) } }));
pArgs[2] <<= aProperties;
}
} elseif ( pImpl->xInputStream.is() )
{ // since the storage is based on temporary stream we open it always read-write
pArgs[0] <<= pImpl->xInputStream;
pArgs[1] <<= embed::ElementModes::READ;
pImpl->bStorageBasedOnInStream = true;
} else
{
CloseStreams_Impl();
pArgs[0] <<= pImpl->m_aName;
pArgs[1] <<= embed::ElementModes::READ;
pImpl->bStorageBasedOnInStream = false;
}
try
{
pImpl->xStorage.set( ::comphelper::OStorageHelper::GetStorageFactory()->createInstanceWithArguments( aArgs ),
uno::UNO_QUERY );
} catch( const uno::Exception& )
{ // impossibility to create the storage is no error
}
bool bResetStorage = false; if ( pVersion && pVersion->GetValue() )
{ // Read all available versions if ( pImpl->aVersions.hasElements() )
{ // Search for the version fits the comment // The versions are numbered starting with 1, versions with // negative versions numbers are counted backwards from the // current version short nVersion = pVersion->GetValue(); if ( nVersion<0 )
nVersion = static_cast<short>(pImpl->aVersions.getLength()) + nVersion; else// nVersion > 0; pVersion->GetValue() != 0 was the condition to this block
nVersion--;
const util::RevisionTag& rTag = pImpl->aVersions[nVersion];
{ // Open SubStorage for all versions
uno::Reference < embed::XStorage > xSub = pImpl->xStorage->openStorageElement( u"Versions"_ustr,
embed::ElementModes::READ );
DBG_ASSERT( xSub.is(), "Version list, but no Versions!" );
// There the version is stored as packed Stream
uno::Reference < io::XStream > xStr = xSub->openStreamElement( rTag.Identifier, embed::ElementModes::READ );
std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream( xStr )); if ( pStream && pStream->GetError() == ERRCODE_NONE )
{ // Unpack Stream in TempDir const OUString aTmpName = ::utl::CreateTempURL();
SvFileStream aTmpStream( aTmpName, SFX_STREAM_READWRITE );
const uno::Reference<embed::XStorage> & SfxMedium::GetScriptingStorageToSign_Impl()
{ // this was set when it was initially loaded if (pImpl->m_bODFWholesomeEncryption)
{ // (partial) scripting signature can only be in inner storage! // Note: a "PackageFormat" storage like pImpl->xStorage doesn't work // (even if it's not encrypted) because it hides the "META-INF" dir. // This "ZipFormat" storage is used only read-only; a writable one is // created manually in SignContents_Impl(). if (!pImpl->m_xODFDecryptedInnerZipStorage.is())
{
GetStorage(false); // don't care about xStorage here because Zip is readonly
SAL_WARN_IF(!pImpl->m_xODFDecryptedInnerPackageStream.is(), "sfx.doc", "no inner package stream?"); if (pImpl->m_xODFDecryptedInnerPackageStream.is())
{
pImpl->m_xODFDecryptedInnerZipStorage =
::comphelper::OStorageHelper::GetStorageOfFormatFromInputStream(
ZIP_STORAGE_FORMAT_STRING,
pImpl->m_xODFDecryptedInnerPackageStream->getInputStream(), {},
IsRepairPackage());
}
} return pImpl->m_xODFDecryptedInnerZipStorage;
} else
{ return GetZipStorageToSign_Impl(true);
}
}
// note: currently nobody who calls this with "false" writes into an ODF // storage that is returned here, that is only for OOXML
uno::Reference< embed::XStorage > const & SfxMedium::GetZipStorageToSign_Impl( bool bReadOnly )
{ if ( !GetErrorIgnoreWarning() && !pImpl->m_xZipStorage.is() )
{
GetMedium_Impl();
try
{ // we can not sign document if there is no stream // should it be possible at all? if ( !bReadOnly && pImpl->xStream.is() )
{
pImpl->m_xZipStorage = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
ZIP_STORAGE_FORMAT_STRING, pImpl->xStream, css::embed::ElementModes::READWRITE,
{}, IsRepairPackage());
} elseif ( pImpl->xInputStream.is() )
{
pImpl->m_xZipStorage
= ::comphelper::OStorageHelper::GetStorageOfFormatFromInputStream(
ZIP_STORAGE_FORMAT_STRING, pImpl->xInputStream, {}, IsRepairPackage());
}
} catch( const uno::Exception& )
{
SAL_WARN( "sfx.doc", "No possibility to get readonly version of storage from medium!" );
}
if ( GetErrorIgnoreWarning() ) // do not remove warnings
ResetError();
}
void SfxMedium::CloseStorage()
{ if ( pImpl->xStorage.is() )
{
uno::Reference < lang::XComponent > xComp = pImpl->xStorage; // in the salvage mode the medium does not own the storage if ( pImpl->bDisposeStorage && !pImpl->m_bSalvageMode )
{ try {
xComp->dispose();
} catch( const uno::Exception& )
{
SAL_WARN( "sfx.doc", "Medium's storage is already disposed!" );
}
}
Reference< XInputStream > aOrigInput = aTransactCont.openStream();
aOriginalContent.writeStream( aOrigInput, true ); returntrue;
} 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;
}
if ( pImpl->xStorage.is() )
{ if ( !GetErrorIgnoreWarning() )
{
uno::Reference < embed::XTransactedObject > xTrans( pImpl->xStorage, uno::UNO_QUERY ); if ( xTrans.is() )
{ try
{
xTrans->commit();
CloseZipStorage_Impl();
bResult = true;
} catch ( const embed::UseBackupException& aBackupExc )
{ // since the temporary file is created always now, the scenario is close to be impossible if ( !pImpl->pTempFile )
{
OSL_ENSURE( !pImpl->m_aBackupURL.isEmpty(), "No backup on storage commit!" ); if ( !pImpl->m_aBackupURL.isEmpty()
&& ::ucbhelper::Content::create( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ),
xDummyEnv, comphelper::getProcessComponentContext(),
aOriginalContent ) )
{ // use backup to restore the file // the storage has already disconnected from original location
CloseAndReleaseStreams_Impl(); if ( !UseBackupToRestore_Impl( aOriginalContent, xDummyEnv ) )
{ // connect the medium to the temporary file of the storage
pImpl->aContent = ::ucbhelper::Content();
pImpl->m_aName = aBackupExc.TemporaryFileURL;
OSL_ENSURE( !pImpl->m_aName.isEmpty(), "The exception _must_ contain the temporary URL!" );
}
}
}
try
{ // tdf#60237 - if the OverWrite property of the MediaDescriptor is set to false, // try to write the file before trying to rename or copy it if (!(bOverWrite
&& ::utl::UCBContentHelper::IsDocument(
aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE))))
{
Reference< XInputStream > aTempInput = aTempCont.openStream();
aOriginalContent.writeStream( aTempInput, bOverWrite );
bResult = true;
} else {
OUString aSourceMainURL = aSource.GetMainURL(INetURLObject::DecodeMechanism::NONE);
OUString aDestMainURL = aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE);
sal_uInt64 nAttributes = GetDefaultFileAttributes(aDestMainURL); if (IsFileMovable(aDest)
&& osl::File::replace(aSourceMainURL, aDestMainURL) == osl::FileBase::E_None)
{ if (nAttributes) // Adjust attributes, source might be created with // the osl_File_OpenFlag_Private flag.
osl::File::setAttributes(aDestMainURL, nAttributes);
bResult = true;
} else
{ if( pImpl->m_aBackupURL.isEmpty() )
DoInternalBackup_Impl( aOriginalContent );
// if the document had no password it should be stored without password // if the document had password it should be stored with the same password // otherwise the stream copying can not be done const SfxStringItem* pNewPassItem = aTargetSet.GetItem(SID_PASSWORD, false); const SfxStringItem* pOldPassItem = GetItemSet().GetItem(SID_PASSWORD, false); if ( ( !pNewPassItem && !pOldPassItem )
|| ( pNewPassItem && pOldPassItem && pNewPassItem->GetValue() == pOldPassItem->GetValue() ) )
{ // the filter must be the same const SfxStringItem* pNewFilterItem = aTargetSet.GetItem(SID_FILTER_NAME, false); const SfxStringItem* pOldFilterItem = GetItemSet().GetItem(SID_FILTER_NAME, false); if ( pNewFilterItem && pOldFilterItem && pNewFilterItem->GetValue() == pOldFilterItem->GetValue() )
{ // get the input stream and copy it // in case of success return true
uno::Reference< io::XInputStream > xInStream = GetInputStream();
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(); elseif ( !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!" );
}
// 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();
// source is the temp file written so far
INetURLObject aSource( aNameURL );
// a special case, an interaction handler should be used for // authentication in case it is available
Reference< css::ucb::XCommandEnvironment > xComEnv; bool bForceInteractionHandler = GetURLObject().isAnyKnownWebDAVScheme();
Reference< css::task::XInteractionHandler > xInteractionHandler = GetInteractionHandler(bForceInteractionHandler); if (xInteractionHandler.is())
xComEnv = new ::ucbhelper::CommandEnvironment( xInteractionHandler,
Reference< css::ucb::XProgressHandler >() );
if (!pImpl->m_bDisableFileSync)
{ // Hideous - no clean way to do this, so we re-open the file just to fsync it
osl::File aFile( aDestURL ); if ( aFile.open( osl_File_OpenFlag_Write ) == osl::FileBase::E_None )
{
aFile.sync();
SAL_INFO( "sfx.doc", "fsync'd saved file '" << aDestURL << "'" );
aFile.close();
}
}
} else
{ // create content for the parent folder and call transfer on that content with the source content // and the destination file name as parameters
::ucbhelper::Content aSourceContent;
::ucbhelper::Content aTransferContent;
::ucbhelper::Content aDestContent;
(void)::ucbhelper::Content::create( aDestURL, xComEnv, comphelper::getProcessComponentContext(), aDestContent ); // For checkin, we need the object URL, not the parent folder: if ( !IsInCheckIn( ) )
{ // Get the parent URL from the XChild if possible: why would the URL necessarily have // a hierarchical path? It's not always the case for CMIS.
Reference< css::container::XChild> xChild( aDestContent.get(), uno::UNO_QUERY );
OUString sParentUrl; if ( xChild.is( ) )
{
Reference< css::ucb::XContent > xParent( xChild->getParent( ), uno::UNO_QUERY ); if ( xParent.is( ) )
{
sParentUrl = xParent->getIdentifier( )->getContentIdentifier();
}
}
if ( sParentUrl.isEmpty() )
aDestURL = aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ); // adjust to above aDest.removeSegment() else
aDestURL = sParentUrl;
}
// LongName wasn't defined anywhere, only used here... get the Title instead // as it's less probably empty
OUString aFileName;
OUString sObjectId; try
{
Any aAny = aDestContent.getPropertyValue(u"Title"_ustr);
aAny >>= aFileName;
aAny = aDestContent.getPropertyValue(u"ObjectId"_ustr);
aAny >>= sObjectId;
} catch (uno::Exception const&)
{
SAL_INFO("sfx.doc", "exception while getting Title or ObjectId");
} if ( aFileName.isEmpty() )
aFileName = GetURLObject().getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
if ( !pImpl->m_eError || pImpl->m_eError.IsWarning() )
{ // free resources, otherwise the transfer may fail if ( pImpl->xStorage.is() )
CloseStorage();
if ( !sResultURL.isEmpty( ) ) // Likely to happen only for checkin
SwitchDocumentToFile( sResultURL ); try
{ if ( GetURLObject().isAnyKnownWebDAVScheme() &&
eOperation == ::ucbhelper::InsertOperation::Copy )
{ // tdf#95272 try to re-issue a lock command when a new file is created. // This may be needed because some WebDAV servers fail to implement the // 'LOCK on unallocated reference', see issue comment: // <https://bugs.documentfoundation.org/show_bug.cgi?id=95792#c8> // and specification at: // <http://tools.ietf.org/html/rfc4918#section-7.3> // If the WebDAV resource is already locked by this LO instance, nothing will // happen, e.g. the LOCK method will not be sent to the server.
::ucbhelper::Content aLockContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
aLockContent.lock();
}
} catch ( css::uno::Exception & )
{
TOOLS_WARN_EXCEPTION( "sfx.doc", "LOCK not working while re-issuing it" );
}
} catch ( const css::ucb::CommandAbortedException& )
{
pImpl->m_eError = ERRCODE_ABORT;
} catch ( const css::ucb::CommandFailedException& )
{
pImpl->m_eError = ERRCODE_ABORT;
} catch ( const css::ucb::InteractiveIOException& r )
{ if ( r.Code == IOErrorCode_ACCESS_DENIED )
pImpl->m_eError = ERRCODE_IO_ACCESSDENIED; elseif ( r.Code == IOErrorCode_NOT_EXISTING )
pImpl->m_eError = ERRCODE_IO_NOTEXISTS; elseif ( r.Code == IOErrorCode_CANT_READ )
pImpl->m_eError = ERRCODE_IO_CANTREAD; else
pImpl->m_eError = ERRCODE_IO_GENERAL;
} catch ( const css::uno::Exception& )
{
pImpl->m_eError = ERRCODE_IO_GENERAL;
}
// do not switch from temporary file in case of nonfile protocol
}
}
if ( ( !pImpl->m_eError || pImpl->m_eError.IsWarning() ) && !pImpl->pTempFile )
{ // without a TempFile the physical and logical name should be the same after successful transfer if (osl::FileBase::getSystemPathFromFileURL(
GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), pImpl->m_aName )
!= osl::FileBase::E_None)
{
pImpl->m_aName.clear();
}
pImpl->m_bSalvageMode = false;
}
}
void SfxMedium::DoInternalBackup_Impl( const ::ucbhelper::Content& aOriginalContent,
std::u16string_view aPrefix,
std::u16string_view aExtension, const OUString& aDestDir )
{ if ( !pImpl->m_aBackupURL.isEmpty() ) return; // the backup was done already
// the copying to the backup catalog failed ( for example because // of using an encrypted partition as target catalog ) // since the user did not specify to make backup explicitly // office should try to make backup in another place, // target catalog does not look bad for this case ( and looks // to be the only way for encrypted partitions )
void SfxMedium::DoBackup_Impl(bool bForceUsingBackupPath)
{ // source file name is the logical name of this medium
INetURLObject aSource( GetURLObject() );
// there is nothing to backup in case source file does not exist if ( !::utl::UCBContentHelper::IsDocument( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) ) return;
// create a content for the source file
::ucbhelper::Content aSourceContent; if ( ::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aSourceContent ) )
{ try
{ // do the transfer ( copy source file to backup dir )
OUString sMimeType = pImpl->getFilterMimeType();
aContent.transferContent( aSourceContent,
::ucbhelper::InsertOperation::Copy,
aFileName,
NameClash::OVERWRITE,
sMimeType );
pImpl->m_aBackupURL = aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE );
pImpl->m_bRemoveBackup = false;
bSuccess = true;
} catch ( const css::uno::Exception& )
{
}
}
}
}
if ( !bSuccess )
{ // in case a webdav server prevents file creation, or a partition is full, or whatever... if (bOnErrorRetryUsingBackupPath) return DoBackup_Impl(/*bForceUsingBackupPath=*/true);
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
{
if ( !pImpl->pTempFile && pImpl->m_aName.isEmpty() )
{ // the medium is still based on the original file, it makes sense to initialize the streams if ( pImpl->m_xLockingStream.is() )
pImpl->xStream = pImpl->m_xLockingStream;
if ( xInputStream.is() )
pImpl->xInputStream = std::move(xInputStream);
// 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 ( 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();
} elseif ( 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;
}
if ( !bFromTempFile )
{ //TODO/MBA: need support for SID_STREAM if ( pImpl->xStream.is() )
GetItemSet().Put( SfxUnoAnyItem( SID_STREAM, Any( pImpl->xStream ) ) );
//TODO/MBA: ErrorHandling - how to transport error from MediaDescriptor if ( !GetErrorIgnoreWarning() && !pImpl->xStream.is() && !pImpl->xInputStream.is() )
SetError(ERRCODE_IO_ACCESSDENIED);
/** Sets m_aLogicName to a valid URL and if available sets the physical name m_aName to the file name.
*/ void SfxMedium::Init_Impl()
{
Reference< XOutputStream > rOutStream;
// TODO/LATER: handle lifetime of storages
pImpl->bDisposeStorage = false;
// try to convert the URL into a physical name - but never change a physical name // physical name may be set if the logical name is changed after construction if ( pImpl->m_aName.isEmpty() )
osl::FileBase::getSystemPathFromFileURL( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), pImpl->m_aName ); else
{
DBG_ASSERT( pSalvageItem, "Suspicious change of logical name!" );
}
}
}
if ( pSalvageItem )
{
std::unique_lock<std::recursive_mutex> chkEditLock; if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
chkEditLock
= std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex));
pImpl->m_aLogicName = pSalvageItem->GetValue();
pImpl->m_pURLObj.reset(); if (chkEditLock.owns_lock())
chkEditLock.unlock();
pImpl->m_bSalvageMode = true;
}
// in case output stream is by mistake here // clear the reference const SfxUnoAnyItem* pOutStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_OUTPUTSTREAM, false); if( pOutStreamItem
&& ( !( pOutStreamItem->GetValue() >>= rOutStream )
|| !pImpl->m_aLogicName.startsWith("private:stream")) )
{
pImpl->m_pSet->ClearItem( SID_OUTPUTSTREAM );
SAL_WARN( "sfx.doc", "Unexpected Output stream parameter!" );
}
if (!pImpl->m_aLogicName.isEmpty())
{ // if the logic name is set it should be set in MediaDescriptor as well const SfxStringItem* pFileNameItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_FILE_NAME, false); if ( !pFileNameItem )
{ // let the ItemSet be created if necessary
GetItemSet().Put(
SfxStringItem(
SID_FILE_NAME, INetURLObject( pImpl->m_aLogicName ).GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) );
}
}
void SfxMedium::UnlockFile( bool bReleaseLockStream )
{ #if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT
(void) bReleaseLockStream; #else // check if webdav if ( GetURLObject().isAnyKnownWebDAVScheme() )
{ // do nothing if WebDAV locking if disabled // (shouldn't happen because we already skipped locking, // see LockOrigFileOnDemand, but just in case ...) if (!IsWebDAVLockingUsed()) return;
if ( pImpl->m_bLocked )
{ // an interaction handler should be used for authentication, if needed try {
uno::Reference< css::task::XInteractionHandler > xHandler = GetInteractionHandler( true );
uno::Reference< css::ucb::XCommandEnvironment > xComEnv = new ::ucbhelper::CommandEnvironment( xHandler,
Reference< css::ucb::XProgressHandler >() );
ucbhelper::Content aContentToUnlock( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext());
pImpl->m_bLocked = false; //check if WebDAV unlock was explicitly disabled if ( !pImpl->m_bDisableUnlockWebDAV )
aContentToUnlock.unlock();
} catch ( uno::Exception& )
{
TOOLS_WARN_EXCEPTION( "sfx.doc", "Locking exception: WebDAV while trying to lock the file");
}
} return;
}
try
{
pImpl->m_bLocked = false; // TODO/LATER: A warning could be shown in case the file is not the own one
aLockFile.RemoveFile();
} catch (const io::WrongFormatException&)
{ // erase the empty or corrupt file
aLockFile.RemoveFileDirectly();
}
} catch( const uno::Exception& )
{}
try
{
pImpl->m_bLocked = false; // TODO/LATER: A warning could be shown in case the file is not the own one
aMSOLockFile.RemoveFile();
} catch (const io::WrongFormatException&)
{ // erase the empty or corrupt file
aMSOLockFile.RemoveFileDirectly();
}
} catch( const uno::Exception& )
{}
pImpl->m_bMSOLockFileCreated = false; #endif
}
// if the locking stream is closed here the related member should be cleaned if ( pImpl->xStream == pImpl->m_xLockingStream )
pImpl->m_xLockingStream.clear();
}
// The probably existing SvStream wrappers should be closed first
CloseStreams_Impl();
// in case of salvage mode the storage is based on the streams if ( pImpl->m_bSalvageMode ) return;
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::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;
if ((pItem = pImpl->m_pSet->GetItemIfSet(SID_FILTER_NAME)))
aFilterName = pItem->GetValue();
}
if (aFilterProvider.isEmpty())
{ // This is a conventional filter type.
pImpl->m_pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4FilterName( aFilterName );
} else
{ // This filter is from an external provider such as orcus.
pImpl->m_pCustomFilter = std::make_shared<SfxFilter>(aFilterProvider, aFilterName);
pImpl->m_pFilter = pImpl->m_pCustomFilter;
}
const SfxStringItem* pSalvageItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_DOC_SALVAGE, false); if( pSalvageItem )
{ // QUESTION: there is some treatment of Salvage in Init_Impl; align! if ( !pSalvageItem->GetValue().isEmpty() )
{ // if a URL is provided in SalvageItem that means that the FileName refers to a temporary file // that must be copied here
const SfxStringItem* pFileNameItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_FILE_NAME, false); if (!pFileNameItem) throw uno::RuntimeException();
OUString aNewTempFileURL = SfxMedium::CreateTempCopyWithExt( pFileNameItem->GetValue() ); if ( !aNewTempFileURL.isEmpty() )
{
pImpl->m_pSet->Put( SfxStringItem( SID_FILE_NAME, aNewTempFileURL ) );
pImpl->m_pSet->ClearItem( SID_INPUTSTREAM );
pImpl->m_pSet->ClearItem( SID_STREAM );
pImpl->m_pSet->ClearItem( SID_CONTENT );
} else
{
SAL_WARN( "sfx.doc", "Can not create a new temporary file for crash recovery!" );
}
}
}
// always take BaseURL first, could be overwritten by ItemSet
GetItemSet().Put( SfxStringItem( SID_DOC_BASEURL, rBaseURL ) ); if ( p )
GetItemSet().Put( *p );
}
// always take BaseURL first, could be overwritten by ItemSet
GetItemSet().Put( SfxStringItem( SID_DOC_BASEURL, rBaseURL ) ); if ( p )
GetItemSet().Put( *p );
}
// NOTE: should only be called on main thread
SfxMedium::~SfxMedium()
{
CancelCheckEditableEntry();
// if there is a requirement to clean the backup this is the last possibility to do it
ClearBackup_Impl();
const uno::Sequence < util::RevisionTag >& SfxMedium::GetVersionList( bool _bNoReload )
{ // if the medium has no name, then this medium should represent a new document and can have no version info if ( ( !_bNoReload || !pImpl->m_bVersionsAlreadyLoaded ) && !pImpl->aVersions.hasElements() &&
( !pImpl->m_aName.isEmpty() || !pImpl->m_aLogicName.isEmpty() ) && GetStorage().is() )
{
uno::Reference < document::XDocumentRevisionListPersistence > xReader =
document::DocumentRevisionListPersistence::create( comphelper::getProcessComponentContext() ); try
{
pImpl->aVersions = xReader->load( GetStorage() );
} catch ( const uno::Exception& )
{
}
}
if ( !pImpl->m_bVersionsAlreadyLoaded )
pImpl->m_bVersionsAlreadyLoaded = true;
// To determine a unique name for the stream
std::vector<sal_uInt32> aLongs;
sal_Int32 nLength = pImpl->aVersions.getLength(); for (constauto& rVersion : pImpl->aVersions)
{
sal_uInt32 nVer = static_cast<sal_uInt32>( o3tl::toInt32(rVersion.Identifier.subView(7)));
size_t n; for ( n=0; n<aLongs.size(); ++n ) if ( nVer<aLongs[n] ) break;
aLongs.insert( aLongs.begin()+n, nVer );
}
std::vector<sal_uInt32>::size_type nKey; for ( nKey=0; nKey<aLongs.size(); ++nKey ) if ( aLongs[nKey] > nKey+1 ) break;
bool SfxMedium::IsReadOnly() const
{ // Application-wide read-only mode first if (officecfg::Office::Common::Misc::ViewerAppMode::get()) returntrue;
// a) ReadOnly filter can't produce read/write contents! bool bReadOnly = pImpl->m_pFilter && (pImpl->m_pFilter->GetFilterFlags() & SfxFilterFlags::OPENREADONLY);
// b) if filter allow read/write contents .. check open mode of the storage if (!bReadOnly)
bReadOnly = !( GetOpenMode() & StreamMode::WRITE );
// c) the API can force the readonly state! if (!bReadOnly)
{ const SfxBoolItem* pItem = GetItemSet().GetItem(SID_DOC_READONLY, false); if (pItem)
bReadOnly = pItem->GetValue();
}
bool SfxMedium::SetWritableForUserOnly( const OUString& aURL )
{ // UCB does not allow to allow write access only for the user, // use osl API bool bResult = false;
namespace
{ /// Get the parent directory of a temporary file for output purposes.
OUString GetLogicBase(const INetURLObject& rURL, std::unique_ptr<SfxMedium_Impl> const & pImpl)
{
OUString aLogicBase;
#if HAVE_FEATURE_MACOSX_SANDBOX // In a sandboxed environment we don't want to attempt to create temporary files in the same // directory where the user has selected an output file to be stored. The sandboxed process has // permission only to create the specifically named output file in that directory.
(void) rURL;
(void) pImpl; #else if (!officecfg::Office::Common::Misc::TempFileNextToLocalFile::get()) return aLogicBase;
if (!pImpl->m_bHasEmbeddedObjects // Embedded objects would mean a special base, ignore that.
&& rURL.GetProtocol() == INetProtocol::File && !pImpl->m_pInStream)
{ // Try to create the temp file in the same directory when storing.
INetURLObject aURL(rURL);
aURL.removeSegment();
aLogicBase = aURL.GetMainURL(INetURLObject::DecodeMechanism::WithCharset);
}
#endif// !HAVE_FEATURE_MACOSX_SANDBOX
return aLogicBase;
}
}
void SfxMedium::CreateTempFile( bool bReplace )
{ if ( pImpl->pTempFile )
{ if ( !bReplace ) return;
if ( GetContent().is()
&& GetURLObject().GetProtocol() == INetProtocol::File
&& ::utl::UCBContentHelper::IsDocument( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
{ // if there is already such a document, we should copy it // if it is a file system use OS copy process try
{
uno::Reference< css::ucb::XCommandEnvironment > xComEnv;
INetURLObject aTmpURLObj( aTmpURL );
OUString aFileName = aTmpURLObj.getName( INetURLObject::LAST_SEGMENT, true,
INetURLObject::DecodeMechanism::WithCharset ); if ( !aFileName.isEmpty() && aTmpURLObj.removeSegment() )
{
::ucbhelper::Content aTargetContent( aTmpURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
OUString sMimeType = pImpl->getFilterMimeType();
aTargetContent.transferContent( pImpl->aContent, ::ucbhelper::InsertOperation::Copy, aFileName, NameClash::OVERWRITE, sMimeType );
SetWritableForUserOnly( aTmpURL );
bTransferSuccess = true;
}
} catch( const uno::Exception& )
{}
if ( bTransferSuccess )
{
CloseOutStream();
CloseInStream();
}
}
if ( !bTransferSuccess && pImpl->m_pInStream )
{ // the case when there is no URL-access available or this is a remote protocol // but there is an input stream
GetOutStream(); if ( pImpl->m_pOutStream )
{
std::unique_ptr<char[]> pBuf(newchar [8192]);
ErrCode nErr = ERRCODE_NONE;
bTransferSuccess = true;
CloseInStream();
}
CloseOutStream_Impl();
} else
{ // Quite strange design, but currently it is expected that in this case no transfer happens // TODO/LATER: get rid of this inconsistent part of the call design
bTransferSuccess = true;
CloseInStream();
}
if ( !bTransferSuccess )
{
SetError(ERRCODE_IO_CANTWRITE); return;
}
}
CloseStorage();
}
void SfxMedium::CreateTempFileNoCopy()
{ // this call always replaces the existing temporary file
pImpl->pTempFile.reset();
if (IsOpen() || GetErrorIgnoreWarning())
{
SAL_WARN("sfx.doc", "The medium must be closed by the signer!"); return bChanges;
}
// The component should know if there was a valid document signature, since // it should show a warning in this case
OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(GetStorage()));
uno::Reference< security::XDocumentDigitalSignatures > xSigner(
security::DocumentDigitalSignatures::createWithVersionAndValidSignature(
comphelper::getProcessComponentContext(), aODFVersion, bHasValidDocumentSignature ) ); auto xModelSigner = dynamic_cast<sfx2::DigitalSignatures*>(xSigner.get()); if (!xModelSigner)
{ return bChanges;
}
// the temporary file has been written, commit it to the original file
Commit();
bChanges = true;
}
} elseif (xWriteableZipStor.is())
{ // OOXML.
uno::Reference<io::XStream> xStream;
// We need read-write to be able to add the signature relation. bool bSuccess = xModelSigner->SignModelWithCertificate(
xModel, rSigningContext, GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream);
if (bSuccess)
{
uno::Reference<embed::XTransactedObject> xTransact(xWriteableZipStor, uno::UNO_QUERY_THROW);
xTransact->commit();
// the temporary file has been written, commit it to the original file
Commit();
bChanges = true;
}
} else
{ // Something not ZIP based: e.g. PDF.
std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(GetName(), StreamMode::READ | StreamMode::WRITE));
uno::Reference<io::XStream> xStream(new utl::OStreamWrapper(*pStream)); if (xModelSigner->SignModelWithCertificate(
xModel, rSigningContext, uno::Reference<embed::XStorage>(), xStream))
bChanges = true;
}
}
} catch ( const uno::Exception& )
{
TOOLS_WARN_EXCEPTION("sfx.doc", "Couldn't use signing functionality!");
}
CloseAndRelease();
ResetError();
return bChanges;
}
// note: this is the only function creating scripting signature void SfxMedium::SignContents_Impl(weld::Window* pDialogParent, bool bSignScriptingContent, bool bHasValidDocumentSignature,
SfxViewShell* pViewShell, const std::function<void(bool)>& rCallback, const OUString& aSignatureLineId, const Reference<XCertificate>& xCert, const Reference<XGraphic>& xValidGraphic, const Reference<XGraphic>& xInvalidGraphic, const OUString& aComment)
{ bool bChanges = false;
if (IsOpen() || GetErrorIgnoreWarning())
{
SAL_WARN("sfx.doc", "The medium must be closed by the signer!");
rCallback(bChanges); return;
}
// The component should know if there was a valid document signature, since // it should show a warning in this case
OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(GetStorage()));
uno::Reference< security::XDocumentDigitalSignatures > xSigner(
security::DocumentDigitalSignatures::createWithVersionAndValidSignature(
comphelper::getProcessComponentContext(), aODFVersion, bHasValidDocumentSignature ) ); if (pDialogParent)
xSigner->setParentWindow(pDialogParent->GetXWindow());
auto xModelSigner = dynamic_cast<sfx2::DigitalSignatures*>(xSigner.get());
assert(xModelSigner); if ( bSignScriptingContent )
{ // If the signature has already the document signature it will be removed // after the scripting signature is inserted.
uno::Reference< io::XStream > xStream(
xMetaInf->openStreamElement( xSigner->getScriptingContentSignatureDefaultStreamName(),
embed::ElementModes::READWRITE ),
uno::UNO_SET_THROW );
// note: the storage passed here must be independent from the // xWriteableZipStor because a writable storage can't have 2 // instances of sub-storage for the same directory open, but with // independent storages it somehow works
xModelSigner->SignScriptingContentAsync(
GetScriptingStorageToSign_Impl(), xStream,
[this, xSigner, xMetaInf, xWriteableZipStor,
onSignDocumentContentFinished](bool bRet) { // remove the document signature if any
OUString aDocSigName = xSigner->getDocumentContentSignatureDefaultStreamName(); if ( !aDocSigName.isEmpty() && xMetaInf->hasByName( aDocSigName ) )
xMetaInf->removeElement( aDocSigName );
if (pImpl->m_bODFWholesomeEncryption)
{ // manually copy the inner package to the outer one
uno::Reference<io::XSeekable>(pImpl->m_xODFDecryptedInnerPackageStream, uno::UNO_QUERY_THROW)->seek(0);
uno::Reference<io::XStream> const xEncryptedPackage =
pImpl->m_xODFEncryptedOuterStorage->openStreamElement(
u"encrypted-package"_ustr,
embed::ElementModes::WRITE|embed::ElementModes::TRUNCATE);
comphelper::OStorageHelper::CopyInputToOutput(pImpl->m_xODFDecryptedInnerPackageStream->getInputStream(), xEncryptedPackage->getOutputStream());
xTransact.set(pImpl->m_xODFEncryptedOuterStorage, uno::UNO_QUERY_THROW);
xTransact->commit(); // Commit() below won't do this
}
assert(!pImpl->xStorage.is() // ensure this doesn't overwrite
|| !uno::Reference<util::XModifiable>(pImpl->xStorage, uno::UNO_QUERY_THROW)->isModified()); // the temporary file has been written, commit it to the original file
Commit();
onSignDocumentContentFinished(bRet);
}); return;
} else
{ // Signing the entire document. if (xMetaInf.is())
{ // ODF.
uno::Reference< io::XStream > xStream;
uno::Reference< io::XStream > xScriptingStream; if (GetFilter() && GetFilter()->IsOwnFormat())
{ bool bImplicitScriptSign = officecfg::Office::Common::Security::Scripting::ImplicitScriptSign::get(); if (comphelper::LibreOfficeKit::isActive())
{
bImplicitScriptSign = true;
}
// the temporary file has been written, commit it to the original file
Commit();
}; if (xCert.is())
bSuccess = xSigner->signSignatureLine(
GetZipStorageToSign_Impl(), xStream, aSignatureLineId, xCert,
xValidGraphic, xInvalidGraphic, aComment); else
{ if (xScriptingStream.is())
{
xModelSigner->SetSignScriptingContent(xScriptingStream);
}
// Async, all code before return has to go into the callback.
xModelSigner->SignDocumentContentAsync(GetZipStorageToSign_Impl(),
xStream, pViewShell, [onODFSignDocumentContentFinished, onSignDocumentContentFinished](bool bRet) { if (bRet)
{
onODFSignDocumentContentFinished();
}
auto onOOXMLSignDocumentContentFinished = [this, xWriteableZipStor]() {
uno::Reference<embed::XTransactedObject> xTransact(xWriteableZipStor, uno::UNO_QUERY_THROW);
xTransact->commit();
// the temporary file has been written, commit it to the original file
Commit();
}; bool bSuccess = false; if (xCert.is())
{
bSuccess = xSigner->signSignatureLine(
GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream, aSignatureLineId,
xCert, xValidGraphic, xInvalidGraphic, aComment);
} else
{ // We need read-write to be able to add the signature relation.
xModelSigner->SignDocumentContentAsync(
GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream, pViewShell, [onOOXMLSignDocumentContentFinished, onSignDocumentContentFinished](bool bRet) { if (bRet)
{
onOOXMLSignDocumentContentFinished();
}
OUString SfxMedium::SwitchDocumentToTempFile()
{ // the method returns empty string in case of failure
OUString aResult;
OUString aOrigURL = pImpl->m_aLogicName;
// 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 );
if (bWasReadonly)
{ // set the readonly state back
pImpl->m_nStorOpenMode = SFX_STREAM_READONLY;
GetItemSet().Put(SfxBoolItem(SID_DOC_READONLY, true));
}
bool SfxMedium::SwitchDocumentToFile( const OUString& aURL )
{ // the method is only for storage based documents bool bResult = false;
OUString aOrigURL = pImpl->m_aLogicName;
// should only be called on main thread const std::shared_ptr<std::recursive_mutex>& SfxMedium::GetCheckEditableMutex() const
{ return pImpl->m_pCheckEditableWorkerMutex;
}
// should only be called while holding pImpl->m_pCheckEditableWorkerMutex void SfxMedium::SetWorkerReloadEvent(ImplSVEvent* pEvent)
{
pImpl->m_pReloadEvent = pEvent;
}
// should only be called while holding pImpl->m_pCheckEditableWorkerMutex
ImplSVEvent* SfxMedium::GetWorkerReloadEvent() const
{ return pImpl->m_pReloadEvent;
}
// should only be called on main thread void SfxMedium::AddToCheckEditableWorkerList()
{ if (!pImpl->m_bNotifyWhenEditable) return;
CancelCheckEditableEntry();
if (pImpl->m_pCheckEditableWorkerMutex == nullptr)
{
pImpl->m_pCheckEditableWorkerMutex = std::make_shared<std::recursive_mutex>(); if (pImpl->m_pCheckEditableWorkerMutex == nullptr) return;
}
pImpl->m_pIsDestructed = std::make_shared<bool>(false); if (pImpl->m_pIsDestructed == nullptr) return;
// should only be called on main thread void SfxMedium::CancelCheckEditableEntry(bool bRemoveEvent)
{ if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
{
std::unique_lock<std::recursive_mutex> lock(*(pImpl->m_pCheckEditableWorkerMutex));
if (pImpl->m_pReloadEvent != nullptr)
{ if (bRemoveEvent)
Application::RemoveUserEvent(pImpl->m_pReloadEvent); // make sure destructor doesn't use a freed reference // and reset the event so we can check again
pImpl->m_pReloadEvent = nullptr;
}
/** callback function, which is triggered by worker thread after successfully checking if the file is editable. Sent from <Application::PostUserEvent(..)> Note: This method has to be run in the main thread.
*/
IMPL_STATIC_LINK(SfxMedium, ShowReloadEditableDialog, void*, p, void)
{
SfxMedium* pMed = static_cast<SfxMedium*>(p); if (pMed == nullptr) return;
pMed->CancelCheckEditableEntry(false);
uno::Reference<task::XInteractionHandler> xHandler = pMed->GetInteractionHandler(); if (xHandler.is())
{
OUString aDocumentURL
= pMed->GetURLObject().GetLastName(INetURLObject::DecodeMechanism::WithCharset);
::rtl::Reference<::ucbhelper::InteractionRequest> xInteractionRequestImpl
= new ::ucbhelper::InteractionRequest(uno::Any(document::ReloadEditableRequest(
OUString(), uno::Reference<uno::XInterface>(), aDocumentURL))); if (xInteractionRequestImpl != nullptr)
{
uno::Sequence<uno::Reference<task::XInteractionContinuation>> aContinuations{ new ::ucbhelper::InteractionAbort(xInteractionRequestImpl.get()), new ::ucbhelper::InteractionApprove(xInteractionRequestImpl.get())
};
xInteractionRequestImpl->setContinuations(aContinuations);
xHandler->handle(xInteractionRequestImpl);
::rtl::Reference<::ucbhelper::InteractionContinuation> xSelected
= xInteractionRequestImpl->getSelection(); if (uno::Reference<task::XInteractionApprove>(cppu::getXWeak(xSelected.get()), uno::UNO_QUERY).is())
{ for (SfxViewFrame* pFrame = SfxViewFrame::GetFirst(); pFrame;
pFrame = SfxViewFrame::GetNext(*pFrame))
{ if (pFrame->GetObjectShell()->GetMedium() == pMed)
{ // special case to ensure view isn't set to read-only in // SfxViewFrame::ExecReload_Impl after reloading
pMed->SetOriginallyReadOnly(false);
pFrame->GetDispatcher()->Execute(SID_RELOAD); break;
}
}
}
}
}
}
bool SfxMedium::CheckCanGetLockfile() const
{ #if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT bool bCanReload = true; #else bool bCanReload = false;
::svt::DocumentLockFile aLockFile(GetName());
LockFileEntry aData;
osl::DirectoryItem rItem; auto nError1 = osl::DirectoryItem::get(aLockFile.GetURL(), rItem); if (nError1 == osl::FileBase::E_None)
{ try
{
aData = aLockFile.GetLockData();
} catch (const io::WrongFormatException&)
{ // we get empty or corrupt data returnfalse;
} catch (const uno::Exception&)
{ // locked from other app returnfalse;
}
LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry(); bool bOwnLock
= aOwnData[LockFileComponent::SYSUSERNAME] == aData[LockFileComponent::SYSUSERNAME]; if (bOwnLock
&& aOwnData[LockFileComponent::LOCALHOST] == aData[LockFileComponent::LOCALHOST]
&& aOwnData[LockFileComponent::USERURL] == aData[LockFileComponent::USERURL])
{ // this is own lock from the same installation, it could remain because of crash
bCanReload = true;
}
} elseif (nError1 == osl::FileBase::E_NOENT) // file doesn't exist
{ try
{
aLockFile.CreateOwnLockFile(); try
{ // TODO/LATER: A warning could be shown in case the file is not the own one
aLockFile.RemoveFile();
} catch (const io::WrongFormatException&)
{ try
{ // erase the empty or corrupt file
aLockFile.RemoveFileDirectly();
} catch (const uno::Exception&)
{
}
}
bCanReload = true;
} catch (const uno::Exception&)
{
}
} #endif return bCanReload;
}
// worker thread method, should only be one thread globally void CheckReadOnlyTask::doWork()
{ if (m_xListener == nullptr) return;
while (true)
{
std::unique_lock<std::mutex> termLock(m_xListener->mMutex); if (m_xListener->mCond.wait_for(termLock, std::chrono::seconds(60),
[this] { return m_xListener->bIsTerminated; })) // signalled, spurious wakeups should not be possible return;
// must have timed-out
termLock.unlock();
std::unique_lock<std::mutex> globalLock(g_chkReadOnlyGlobalMutex); for (auto it = g_newReadOnlyDocs.begin(); it != g_newReadOnlyDocs.end(); )
{
g_existingReadOnlyDocs[it->first] = it->second;
it = g_newReadOnlyDocs.erase(it);
} if (g_existingReadOnlyDocs.empty())
{
g_bChkReadOnlyTaskRunning = false; return;
}
globalLock.unlock();
std::unique_lock<std::recursive_mutex> medLock(*(roEntry->_pMutex)); if (*(roEntry->_pIsDestructed) || pMed->GetWorkerReloadEvent() != nullptr) returntrue;
osl::File aFile(
pMed->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::WithCharset)); if (aFile.open(osl_File_OpenFlag_Write) != osl::FileBase::E_None) returnfalse;
if (!pMed->CheckCanGetLockfile()) returnfalse;
if (aFile.close() != osl::FileBase::E_None) returntrue;
// we can load, ask user
ImplSVEvent* pEvent = Application::PostUserEvent(
LINK(nullptr, SfxMedium, ShowReloadEditableDialog), pMed);
pMed->SetWorkerReloadEvent(pEvent); returntrue;
};
for (auto it = g_existingReadOnlyDocs.begin(); it != g_existingReadOnlyDocs.end(); )
{ if (checkForErase(it->first, it->second))
it = g_existingReadOnlyDocs.erase(it); else
++it;
}
}
}
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.110Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-04-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.