office-gobmx/sfx2/source/appl/appdde.cxx
Armin Le Grand (allotropia) 789a737ac9 Remove DeleteItemOnIdlex
There are some CrashReports in 7.6 which have
DeleteItemOnIdle on the stack, but there is nothing
reproducable. So I took a look...

I first thought it's a MCGR regression, due to classes
on the stack. But the Item involved is just random, can
happen with any Item.

Then I thought it may have to do with ITEM refactorings,
but it happens with DeleteItemOnIdle involved, so also
not the case. I already saw DeleteItemOnIdle when doing
these and qualified as 'hack' in the way. already

It is only on Windows and DeleteItemOnIdle is involved.
This again (took a deeper look now) is an old hack to
keep an SfxPoolItem 'alive' for some 'time'. For that,
it triggers an async reschedule which then deletes the
Item when being called. If the Item will be used after
that is pure coincidence - seems to work in most cases.

It seems as if for Windows the timing slightly changed
for some scenarios, so a reschedule is too early. This
can happen with this hack anytime.

DeleteItemOnIdle is used in scenarios where SfxPoolItem*
is e.g. returned, but is *not* anchored, so e.g. not
member of an SfxItemSet. Or in short: Lifetime is not
safe.

DeleteItemOnIdle exists since 1st import, but was
changed to AsyncEvent ca. 4 months ago (see
57145acf9e), so that may
have caused it. It is possible that these errors happen
on Windows since then. Before something more complicated
was used to delete it late, but surely also not really
safe.

Due to ITEM refactor I have the knowledge/tooling to
solve this. It will not be a 1-5 lines fix, but it is
a hack and in the way for further ITEM refactor anyways.
What we have nowadays is a SfxPoolItemHolder -> it's
like an SfxItemSet for a single Item. It safely holds/
controls the lifetime of an SfxPoolItem. It is already
used in quite some places. It helps to solve many hacks,
also the ones putting Items directly to the Pool - due
to there never was an alternative for that. In principle
the ItemPool/ItemSet/Item paradigm was never complete
without SfxPoolItemHolder.

Thus I started to fix that (and remove that hack for
good, sooo many changes over the years, sigh), but as
said is not straightforward. Will have to change
retvals of involved stuff to SfxPoolItemHolder - it's
just two pointers and designed to be copied (one is a
Pool, needed to cleanup when destructing).
CopyConstruct/destroy just counts the RefCnt up/down,
so cheap.

1st version compiling, let's check on gerrit...
Corrected one error in QueryState for securitypage, also
added some security features/asserts.

Change-Id: Ida49fd35ca88ead84b11d93e18b978cb9e395090
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/161083
Tested-by: Jenkins
Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
2023-12-21 21:13:55 +01:00

567 lines
17 KiB
C++

/* -*- 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 <sal/config.h>
#include <string_view>
#include <config_features.h>
#include <rtl/character.hxx>
#include <rtl/malformeduriexception.hxx>
#include <rtl/uri.hxx>
#include <sot/exchange.hxx>
#include <svl/eitem.hxx>
#include <basic/sbstar.hxx>
#include <svl/stritem.hxx>
#include <svl/svdde.hxx>
#include <sfx2/lnkbase.hxx>
#include <sfx2/linkmgr.hxx>
#include <tools/debug.hxx>
#include <tools/urlobj.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <unotools/pathoptions.hxx>
#include <vcl/svapp.hxx>
#include <sfx2/app.hxx>
#include <appdata.hxx>
#include <sfx2/objsh.hxx>
#include <sfx2/viewfrm.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/sfxsids.hrc>
#include <sfx2/docfile.hxx>
#include <ucbhelper/content.hxx>
#include <comphelper/processfactory.hxx>
#if defined(_WIN32)
static OUString SfxDdeServiceName_Impl( const OUString& sIn )
{
OUStringBuffer sReturn(sIn.getLength());
for ( sal_uInt16 n = sIn.getLength(); n; --n )
{
sal_Unicode cChar = sIn[n-1];
if (rtl::isAsciiAlphanumeric(cChar))
sReturn.append(cChar);
}
return sReturn.makeStringAndClear();
}
namespace {
class ImplDdeService : public DdeService
{
public:
explicit ImplDdeService( const OUString& rNm )
: DdeService( rNm )
{}
virtual bool MakeTopic( const OUString& );
virtual OUString Topics();
virtual bool SysTopicExecute( const OUString* pStr );
};
bool lcl_IsDocument( std::u16string_view rContent )
{
using namespace com::sun::star;
bool bRet = false;
INetURLObject aObj( rContent );
DBG_ASSERT( aObj.GetProtocol() != INetProtocol::NotValid, "Invalid URL!" );
try
{
::ucbhelper::Content aCnt( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), uno::Reference< ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() );
bRet = aCnt.isDocument();
}
catch( const uno::Exception& )
{
TOOLS_WARN_EXCEPTION( "sfx.appl", "" );
}
return bRet;
}
}
bool ImplDdeService::MakeTopic( const OUString& rNm )
{
// Workaround for Event after Main() under OS/2
// happens when exiting starts the App again
if ( !Application::IsInExecute() )
return false;
// The Topic rNm is sought, do we have it?
// First only loop over the ObjectShells to find those
// with the specific name:
bool bRet = false;
OUString sNm( rNm.toAsciiLowerCase() );
SfxObjectShell* pShell = SfxObjectShell::GetFirst();
while( pShell )
{
OUString sTmp( pShell->GetTitle(SFX_TITLE_FULLNAME) );
if( sNm == sTmp.toAsciiLowerCase() )
{
SfxGetpApp()->AddDdeTopic( pShell );
bRet = true;
break;
}
pShell = SfxObjectShell::GetNext( *pShell );
}
if( !bRet )
{
bool abs;
OUString url;
try {
url = rtl::Uri::convertRelToAbs(SvtPathOptions().GetWorkPath(), rNm);
abs = true;
} catch (rtl::MalformedUriException &) {
abs = false;
}
if ( abs && lcl_IsDocument( url ) )
{
// File exists? then try to load it:
SfxStringItem aName( SID_FILE_NAME, url );
SfxBoolItem aNewView(SID_OPEN_NEW_VIEW, true);
SfxBoolItem aSilent(SID_SILENT, true);
const SfxPoolItemHolder aResult(SfxGetpApp()->GetDispatcher_Impl()->ExecuteList(SID_OPENDOC,
SfxCallMode::SYNCHRON,
{ &aName, &aNewView, &aSilent }));
if( auto const item = dynamic_cast< const SfxViewFrameItem *>(aResult.getItem());
item &&
item->GetFrame() &&
nullptr != ( pShell = item->GetFrame()->GetObjectShell() ) )
{
SfxGetpApp()->AddDdeTopic( pShell );
bRet = true;
}
}
}
return bRet;
}
OUString ImplDdeService::Topics()
{
OUString sRet;
if( GetSysTopic() )
sRet += GetSysTopic()->GetName();
SfxObjectShell* pShell = SfxObjectShell::GetFirst();
while( pShell )
{
if( SfxViewFrame::GetFirst( pShell ) )
{
if( !sRet.isEmpty() )
sRet += "\t";
sRet += pShell->GetTitle(SFX_TITLE_FULLNAME);
}
pShell = SfxObjectShell::GetNext( *pShell );
}
if( !sRet.isEmpty() )
sRet += "\r\n";
return sRet;
}
bool ImplDdeService::SysTopicExecute( const OUString* pStr )
{
return SfxApplication::DdeExecute( *pStr );
}
#endif
class SfxDdeDocTopic_Impl : public DdeTopic
{
#if defined(_WIN32)
public:
SfxObjectShell* pSh;
DdeData aData;
css::uno::Sequence< sal_Int8 > aSeq;
explicit SfxDdeDocTopic_Impl( SfxObjectShell* pShell )
: DdeTopic( pShell->GetTitle(SFX_TITLE_FULLNAME) ), pSh( pShell )
{}
virtual DdeData* Get( SotClipboardFormatId ) override;
virtual bool Put( const DdeData* ) override;
virtual bool Execute( const OUString* ) override;
virtual bool StartAdviseLoop() override;
virtual bool MakeItem( const OUString& rItem ) override;
#endif
};
#if defined(_WIN32)
namespace {
/* [Description]
Checks if 'rCmd' of the event 'rEvent' is (without '(') and then assemble
this data into a <ApplicationEvent>, which is then executed through
<Application::AppEvent()>. If 'rCmd' is the given event 'rEvent', then
TRUE is returned, otherwise FALSE.
[Example]
rCmd = "Open(\"d:\doc\doc.sdw\")"
rEvent = "Open"
*/
bool SfxAppEvent_Impl( const OUString& rCmd, std::u16string_view rEvent,
ApplicationEvent::Type eType )
{
OUString sEvent(OUString::Concat(rEvent) + "(");
if (rCmd.startsWithIgnoreAsciiCase(sEvent))
{
sal_Int32 start = sEvent.getLength();
if ( rCmd.getLength() - start >= 2 )
{
// Transform into the ApplicationEvent Format
//TODO: I /assume/ that rCmd should match the syntax of
// <http://msdn.microsoft.com/en-us/library/ms648995.aspx>
// "WM_DDE_EXECUTE message" but does not (handle commands enclosed
// in [...]; handle commas separating multiple arguments; handle
// double "", ((, )), [[, ]] in quoted arguments); see also the mail
// thread starting at <http://lists.freedesktop.org/archives/
// libreoffice/2013-July/054779.html> "DDE on Windows."
std::vector<OUString> aData;
for ( sal_Int32 n = start; n < rCmd.getLength() - 1; )
{
// Resiliently read arguments either starting with " and
// spanning to the next " (if any; TODO: do we need to undo any
// escaping within the string?) or with neither " nor SPC and
// spanning to the next SPC (if any; TODO: is this from not
// wrapped in "..." relevant? it would have been parsed by the
// original code even if that was only by accident, so I left it
// in), with runs of SPCs treated like single ones:
switch ( rCmd[n] )
{
case '"':
{
sal_Int32 i = rCmd.indexOf('"', ++n);
if (i < 0 || i > rCmd.getLength() - 1) {
i = rCmd.getLength() - 1;
}
aData.push_back(rCmd.copy(n, i - n));
n = i + 1;
break;
}
case ' ':
++n;
break;
default:
{
sal_Int32 i = rCmd.indexOf(' ', n);
if (i < 0 || i > rCmd.getLength() - 1) {
i = rCmd.getLength() - 1;
}
aData.push_back(rCmd.copy(n, i - n));
n = i + 1;
break;
}
}
}
GetpApp()->AppEvent( ApplicationEvent(eType, std::move(aData)) );
return true;
}
}
return false;
}
}
/* Description]
This method can be overridden by application developers, to receive
DDE-commands directed to their SfxApplication subclass.
The base implementation understands the API functionality of the
relevant SfxApplication subclass in BASIC syntax. Return values can
not be transferred, unfortunately.
*/
bool SfxApplication::DdeExecute( const OUString& rCmd ) // Expressed in our BASIC-Syntax
{
// Print or Open-Event?
if ( !( SfxAppEvent_Impl( rCmd, u"Print", ApplicationEvent::Type::Print ) ||
SfxAppEvent_Impl( rCmd, u"Open", ApplicationEvent::Type::Open ) ) )
{
// all others are BASIC
StarBASIC* pBasic = GetBasic();
DBG_ASSERT( pBasic, "Where is the Basic???" );
SbxVariable* pRet = pBasic->Execute( rCmd );
if( !pRet )
{
SbxBase::ResetError();
return false;
}
}
return true;
}
/* [Description]
This method can be overridden by application developers, to receive
DDE-commands directed to the their SfxApplication subclass.
The base implementation does nothing and returns 0.
*/
bool SfxObjectShell::DdeExecute( const OUString& rCmd ) // Expressed in our BASIC-Syntax
{
#if !HAVE_FEATURE_SCRIPTING
(void) rCmd;
#else
StarBASIC* pBasic = GetBasic();
DBG_ASSERT( pBasic, "Where is the Basic???" ) ;
SbxVariable* pRet = pBasic->Execute( rCmd );
if( !pRet )
{
SbxBase::ResetError();
return false;
}
#endif
return true;
}
/* [Description]
This method can be overridden by application developers, to receive
DDE-data-requests directed to their SfxApplication subclass.
The base implementation provides no data and returns false.
*/
bool SfxObjectShell::DdeGetData( const OUString&, // the Item to be addressed
const OUString&, // in: Format
css::uno::Any& )// out: requested data
{
return false;
}
/* [Description]
This method can be overridden by application developers, to receive
DDE-data directed to their SfxApplication subclass.
The base implementation is not receiving any data and returns false.
*/
bool SfxObjectShell::DdeSetData( const OUString&, // the Item to be addressed
const OUString&, // in: Format
const css::uno::Any& )// out: requested data
{
return false;
}
#endif
/* [Description]
This method can be overridden by application developers, to establish
a DDE-hotlink to their SfxApplication subclass.
The base implementation is not generate a link and returns 0.
*/
::sfx2::SvLinkSource* SfxObjectShell::DdeCreateLinkSource( const OUString& ) // the Item to be addressed
{
return nullptr;
}
void SfxObjectShell::ReconnectDdeLink(SfxObjectShell& /*rServer*/)
{
}
void SfxObjectShell::ReconnectDdeLinks(SfxObjectShell& rServer)
{
SfxObjectShell* p = GetFirst(nullptr, false);
while (p)
{
if (&rServer != p)
p->ReconnectDdeLink(rServer);
p = GetNext(*p, nullptr, false);
}
}
bool SfxApplication::InitializeDde()
{
int nError = 0;
#if defined(_WIN32)
DBG_ASSERT( !pImpl->pDdeService,
"Dde can not be initialized multiple times" );
pImpl->pDdeService.reset(new ImplDdeService( Application::GetAppName() ));
nError = pImpl->pDdeService->GetError();
if( !nError )
{
// we certainly want to support RTF!
pImpl->pDdeService->AddFormat( SotClipboardFormatId::RTF );
pImpl->pDdeService->AddFormat( SotClipboardFormatId::RICHTEXT );
// Config path as a topic because of multiple starts
INetURLObject aOfficeLockFile( SvtPathOptions().GetUserConfigPath() );
aOfficeLockFile.insertName( u"soffice.lck" );
OUString aService( SfxDdeServiceName_Impl(
aOfficeLockFile.GetMainURL(INetURLObject::DecodeMechanism::ToIUri) ) );
aService = aService.toAsciiUpperCase();
pImpl->pDdeService2.reset( new ImplDdeService( aService ));
pImpl->pTriggerTopic.reset(new SfxDdeTriggerTopic_Impl);
pImpl->pDdeService2->AddTopic( *pImpl->pTriggerTopic );
}
#endif
return !nError;
}
void SfxAppData_Impl::DeInitDDE()
{
pTriggerTopic.reset();
pDdeService2.reset();
maDocTopics.clear();
pDdeService.reset();
}
#if defined(_WIN32)
void SfxApplication::AddDdeTopic( SfxObjectShell* pSh )
{
//OV: DDE is disconnected in server mode!
if( pImpl->maDocTopics.empty() )
return;
// prevent double submit
OUString sShellNm;
bool bFnd = false;
for (size_t n = pImpl->maDocTopics.size(); n;)
{
if( pImpl->maDocTopics[ --n ]->pSh == pSh )
{
// If the document is untitled, is still a new Topic is created!
if( !bFnd )
{
bFnd = true;
sShellNm = pSh->GetTitle(SFX_TITLE_FULLNAME).toAsciiLowerCase();
}
OUString sNm( pImpl->maDocTopics[ n ]->GetName() );
if( sShellNm == sNm.toAsciiLowerCase() )
return ;
}
}
SfxDdeDocTopic_Impl *const pTopic = new SfxDdeDocTopic_Impl(pSh);
pImpl->maDocTopics.push_back(pTopic);
pImpl->pDdeService->AddTopic( *pTopic );
}
#endif
void SfxApplication::RemoveDdeTopic( SfxObjectShell const * pSh )
{
#if defined(_WIN32)
//OV: DDE is disconnected in server mode!
if( pImpl->maDocTopics.empty() )
return;
for (size_t n = pImpl->maDocTopics.size(); n; )
{
SfxDdeDocTopic_Impl *const pTopic = pImpl->maDocTopics[ --n ];
if (pTopic->pSh == pSh)
{
pImpl->pDdeService->RemoveTopic( *pTopic );
delete pTopic;
pImpl->maDocTopics.erase( pImpl->maDocTopics.begin() + n );
}
}
#else
(void) pSh;
#endif
}
const DdeService* SfxApplication::GetDdeService() const
{
return pImpl->pDdeService.get();
}
DdeService* SfxApplication::GetDdeService()
{
return pImpl->pDdeService.get();
}
#if defined(_WIN32)
DdeData* SfxDdeDocTopic_Impl::Get(SotClipboardFormatId nFormat)
{
OUString sMimeType( SotExchange::GetFormatMimeType( nFormat ));
css::uno::Any aValue;
bool bRet = pSh->DdeGetData( GetCurItem(), sMimeType, aValue );
if( bRet && aValue.hasValue() && ( aValue >>= aSeq ) )
{
aData = DdeData( aSeq.getConstArray(), aSeq.getLength(), nFormat );
return &aData;
}
aSeq.realloc( 0 );
return nullptr;
}
bool SfxDdeDocTopic_Impl::Put( const DdeData* pData )
{
aSeq = css::uno::Sequence< sal_Int8 >(
static_cast<sal_Int8 const *>(pData->getData()), pData->getSize() );
bool bRet;
if( aSeq.getLength() )
{
css::uno::Any aValue;
aValue <<= aSeq;
OUString sMimeType( SotExchange::GetFormatMimeType( pData->GetFormat() ));
bRet = pSh->DdeSetData( GetCurItem(), sMimeType, aValue );
}
else
bRet = false;
return bRet;
}
bool SfxDdeDocTopic_Impl::Execute( const OUString* pStr )
{
return pStr && pSh->DdeExecute( *pStr );
}
bool SfxDdeDocTopic_Impl::MakeItem( const OUString& rItem )
{
AddItem( DdeItem( rItem ) );
return true;
}
bool SfxDdeDocTopic_Impl::StartAdviseLoop()
{
bool bRet = false;
::sfx2::SvLinkSource* pNewObj = pSh->DdeCreateLinkSource( GetCurItem() );
if( pNewObj )
{
// then we also establish a corresponding SvBaseLink
OUString sNm, sTmp( Application::GetAppName() );
::sfx2::MakeLnkName( sNm, &sTmp, pSh->GetTitle(SFX_TITLE_FULLNAME), GetCurItem() );
new ::sfx2::SvBaseLink( sNm, sfx2::SvBaseLinkObjectType::DdeExternal, pNewObj );
bRet = true;
}
return bRet;
}
#endif
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */