The arguments for a macro when invoked by the dispatch provider get
converted from a set of PropertyValues to a sequence of Anys containing
just the values. Since the commit below, the TransformItems step was
changed to store the PropertyValues in an unordered_map which makes the
order of the values unpredictable. If the dispatch provider is used to
execute a macro then the order of the arguments becomes important
because the macro has no other way to determine which argument
represents which property. In order to maintain the previous behaviour,
the dispatch method now reorders the arguments so that they have the
same order as before. The previous order was determined by the order the
properties were handled in TransformItems.
commit e8d73d5307
Author: Mike Kaganski <mike.kaganski@collabora.com>
Date: Wed Dec 3 10:45:35 2025 +0500
Let TransformItems return comphelper::SequenceAsHashMap
Simplifies its usage in all places where the result needs to be
modified.
Change-Id: I37f8d99827e0f1cb2b6e4f4f7735e26fb87f6703
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/195162
Change-Id: Iacf64650842eb62c0a2bd266784aa9c7accf45bd
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209231
Reviewed-by: Neil Roberts <nroberts@libreoffice.org>
Tested-by: Jenkins
476 lines
17 KiB
C++
476 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 "scripthandler.hxx"
|
||
|
||
#include <com/sun/star/frame/DispatchResultEvent.hpp>
|
||
#include <com/sun/star/frame/DispatchResultState.hpp>
|
||
#include <com/sun/star/frame/XController.hpp>
|
||
#include <com/sun/star/frame/XModel.hpp>
|
||
#include <com/sun/star/frame/XModel3.hpp>
|
||
|
||
#include <com/sun/star/document/XEmbeddedScripts.hpp>
|
||
#include <com/sun/star/document/XScriptInvocationContext.hpp>
|
||
|
||
#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
|
||
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
|
||
#include <com/sun/star/script/provider/ScriptFrameworkErrorException.hpp>
|
||
#include <com/sun/star/script/provider/XScriptProviderSupplier.hpp>
|
||
#include <com/sun/star/script/provider/theMasterScriptProviderFactory.hpp>
|
||
#include <com/sun/star/script/provider/ScriptFrameworkErrorType.hpp>
|
||
|
||
#include <sfx2/objsh.hxx>
|
||
#include <sfx2/frame.hxx>
|
||
#include <sfx2/sfxdlg.hxx>
|
||
#include <comphelper/diagnose_ex.hxx>
|
||
|
||
#include <cppuhelper/exc_hlp.hxx>
|
||
#include <cppuhelper/supportsservice.hxx>
|
||
#include <framework/documentundoguard.hxx>
|
||
#include <officecfg/Office/Common.hxx>
|
||
#include <unotools/securityoptions.hxx>
|
||
|
||
#include <com/sun/star/uri/XUriReference.hpp>
|
||
#include <com/sun/star/uri/XVndSunStarScriptUrlReference.hpp>
|
||
#include <com/sun/star/uri/UriReferenceFactory.hpp>
|
||
|
||
#include <memory>
|
||
|
||
using namespace ::com::sun::star;
|
||
using namespace ::com::sun::star::uno;
|
||
using namespace ::com::sun::star::frame;
|
||
using namespace ::com::sun::star::util;
|
||
using namespace ::com::sun::star::beans;
|
||
using namespace ::com::sun::star::lang;
|
||
using namespace ::com::sun::star::script;
|
||
using namespace ::com::sun::star::script::provider;
|
||
using namespace ::com::sun::star::document;
|
||
|
||
namespace scripting_protocolhandler
|
||
{
|
||
|
||
void SAL_CALL ScriptProtocolHandler::initialize(
|
||
const css::uno::Sequence < css::uno::Any >& aArguments )
|
||
{
|
||
if ( m_bInitialised )
|
||
{
|
||
return ;
|
||
}
|
||
|
||
// first argument contains a reference to the frame (may be empty or the desktop,
|
||
// but usually it's a "real" frame)
|
||
if ( aArguments.hasElements() && !( aArguments[ 0 ] >>= m_xFrame ) )
|
||
{
|
||
throw RuntimeException( u"ScriptProtocolHandler::initialize: could not extract reference to the frame"_ustr );
|
||
}
|
||
|
||
ENSURE_OR_THROW( m_xContext.is(), "ScriptProtocolHandler::initialize: No Service Manager available" );
|
||
m_bInitialised = true;
|
||
}
|
||
|
||
namespace
|
||
{
|
||
Sequence<Any> lcl_filterAndOrderScriptArgs(const Sequence<PropertyValue>& rArgs)
|
||
{
|
||
// Copy the Sequence to a local array so we can modify the order
|
||
Sequence<PropertyValue> aArgs = rArgs;
|
||
sal_Int32 nSize = aArgs.getLength();
|
||
PropertyValue* pArgs = aArgs.getArray();
|
||
|
||
// For some reason the script gets passed only the values from the PropertyValue array and not
|
||
// the names so the only way for it to distinguish them is by the order. That means this order
|
||
// is set in stone. See tdf#173160.
|
||
static const std::array<OUString, 6> aPropertyOrder = {
|
||
u"URL"_ustr, u"InteractionHandler"_ustr, u"OpenNewView"_ustr,
|
||
u"FrameName"_ustr, u"MacroExecutionMode"_ustr, u"UpdateDocMode"_ustr,
|
||
};
|
||
sal_Int32 nUsedArgs = 0;
|
||
|
||
// Reorder the property values according to the preset order
|
||
for (const auto& sPropertyName : aPropertyOrder)
|
||
{
|
||
for (sal_Int32 i = nUsedArgs; i < nSize; ++i)
|
||
{
|
||
if (sPropertyName == pArgs[i].Name)
|
||
{
|
||
// Swap this property with the one in the place we want it to be
|
||
if (i != nUsedArgs)
|
||
std::swap(pArgs[i], pArgs[nUsedArgs]);
|
||
++nUsedArgs;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Sometimes we get a propertyval with name = "Referer" or "SynchronMode". These are not actual
|
||
// arguments to be passed to script, but flags describing the call, so ignore. Who thought that
|
||
// passing such "meta-arguments" mixed in with real arguments was a good idea?
|
||
while (nUsedArgs < nSize)
|
||
{
|
||
if (pArgs[nUsedArgs].Name == "Referer" || pArgs[nUsedArgs].Name == "SynchronMode")
|
||
{
|
||
// Move the last argument over the place of this one and then decrease the number of
|
||
// arguments that we’ll use so that the moved one will get ignored
|
||
if (nUsedArgs + 1 < nSize)
|
||
pArgs[nUsedArgs] = std::move(pArgs[nSize - 1]);
|
||
--nSize;
|
||
}
|
||
else
|
||
{
|
||
// Otherwise keep the argument
|
||
++nUsedArgs;
|
||
}
|
||
}
|
||
|
||
Sequence<Any> aValues(nUsedArgs);
|
||
Any* pValues = aValues.getArray();
|
||
|
||
// Move only the values into the final array
|
||
for (sal_Int32 i = 0; i < nUsedArgs; ++i)
|
||
pValues[i] = std::move(pArgs[i].Value);
|
||
|
||
return aValues;
|
||
}
|
||
}
|
||
|
||
Reference< XDispatch > SAL_CALL ScriptProtocolHandler::queryDispatch(
|
||
const URL& aURL, const OUString&, sal_Int32 )
|
||
{
|
||
Reference< XDispatch > xDispatcher;
|
||
// get scheme of url
|
||
|
||
Reference< uri::XUriReferenceFactory > xFac = uri::UriReferenceFactory::create( m_xContext );
|
||
Reference< uri::XUriReference > uriRef = xFac->parse( aURL.Complete );
|
||
if ( uriRef.is() )
|
||
{
|
||
if ( uriRef->getScheme() == "vnd.sun.star.script" )
|
||
{
|
||
xDispatcher = this;
|
||
}
|
||
}
|
||
|
||
return xDispatcher;
|
||
}
|
||
|
||
Sequence< Reference< XDispatch > > SAL_CALL
|
||
ScriptProtocolHandler::queryDispatches(
|
||
const Sequence < DispatchDescriptor >& seqDescriptor )
|
||
{
|
||
sal_Int32 nCount = seqDescriptor.getLength();
|
||
Sequence< Reference< XDispatch > > lDispatcher( nCount );
|
||
std::transform(seqDescriptor.begin(), seqDescriptor.end(), lDispatcher.getArray(),
|
||
[this](const DispatchDescriptor& rDescr) -> Reference<XDispatch> {
|
||
return queryDispatch(rDescr.FeatureURL, rDescr.FrameName, rDescr.SearchFlags); });
|
||
return lDispatcher;
|
||
}
|
||
|
||
void SAL_CALL ScriptProtocolHandler::dispatchWithNotification(
|
||
const URL& aURL, const Sequence < PropertyValue >& lArgs,
|
||
const Reference< XDispatchResultListener >& xListener )
|
||
{
|
||
if (SvtSecurityOptions::IsMacroDisabled())
|
||
return;
|
||
|
||
sal_Int16 aState = css::frame::DispatchResultState::FAILURE;
|
||
Any invokeResult;
|
||
bool bCaughtException = false;
|
||
Any aException;
|
||
|
||
if ( m_bInitialised )
|
||
{
|
||
try
|
||
{
|
||
css::uno::Reference<css::uri::XUriReferenceFactory> urifac(
|
||
css::uri::UriReferenceFactory::create(m_xContext));
|
||
css::uno::Reference<css::uri::XVndSunStarScriptUrlReference> uri(
|
||
urifac->parse(aURL.Complete), css::uno::UNO_QUERY_THROW);
|
||
auto const loc = uri->getParameter(u"location"_ustr);
|
||
bool bIsDocumentScript = loc == "document";
|
||
|
||
if ( bIsDocumentScript )
|
||
{
|
||
// obtain the component for our security check
|
||
Reference< XEmbeddedScripts > xDocumentScripts;
|
||
if ( getScriptInvocation() )
|
||
xDocumentScripts.set( m_xScriptInvocation->getScriptContainer(), UNO_SET_THROW );
|
||
|
||
OSL_ENSURE( xDocumentScripts.is(), "ScriptProtocolHandler::dispatchWithNotification: can't do the security check!" );
|
||
if ( !xDocumentScripts.is() || !xDocumentScripts->getAllowMacroExecution() )
|
||
{
|
||
if ( xListener.is() )
|
||
{
|
||
css::frame::DispatchResultEvent aEvent(
|
||
getXWeak(),
|
||
css::frame::DispatchResultState::FAILURE,
|
||
invokeResult );
|
||
try
|
||
{
|
||
xListener->dispatchFinished( aEvent ) ;
|
||
}
|
||
catch(const RuntimeException &)
|
||
{
|
||
TOOLS_WARN_EXCEPTION("scripting",
|
||
"ScriptProtocolHandler::dispatchWithNotification: caught RuntimeException"
|
||
"while dispatchFinished with failure of the execution");
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Creates a ScriptProvider ( if one is not created already )
|
||
createScriptProvider();
|
||
|
||
Reference< provider::XScript > xFunc =
|
||
m_xScriptProvider->getScript( aURL.Complete );
|
||
ENSURE_OR_THROW( xFunc.is(),
|
||
"ScriptProtocolHandler::dispatchWithNotification: validate xFunc - unable to obtain XScript interface" );
|
||
|
||
|
||
Sequence< Any > inArgs = lcl_filterAndOrderScriptArgs(lArgs);
|
||
Sequence< Any > outArgs;
|
||
Sequence< sal_Int16 > outIndex;
|
||
|
||
// attempt to protect the document against the script tampering with its Undo Context
|
||
std::unique_ptr< ::framework::DocumentUndoGuard > pUndoGuard;
|
||
if ( bIsDocumentScript )
|
||
pUndoGuard.reset( new ::framework::DocumentUndoGuard( m_xScriptInvocation ) );
|
||
|
||
while ( true )
|
||
{
|
||
std::exception_ptr aFirstCaughtException;
|
||
try
|
||
{
|
||
invokeResult = xFunc->invoke( inArgs, outIndex, outArgs );
|
||
aState = css::frame::DispatchResultState::SUCCESS;
|
||
break;
|
||
}
|
||
catch( const provider::ScriptFrameworkErrorException& se )
|
||
{
|
||
if (!aFirstCaughtException)
|
||
aFirstCaughtException = std::current_exception();
|
||
|
||
if ( se.errorType != provider::ScriptFrameworkErrorType::NO_SUCH_SCRIPT )
|
||
// the only condition which allows us to retry is if there is no method with the
|
||
// given name/signature
|
||
std::rethrow_exception(aFirstCaughtException);
|
||
|
||
if ( !inArgs.hasElements() )
|
||
// no chance to retry if we can't strip more in-args
|
||
std::rethrow_exception(std::move(aFirstCaughtException));
|
||
|
||
// strip one argument, then retry
|
||
inArgs.realloc( inArgs.getLength() - 1 );
|
||
}
|
||
}
|
||
}
|
||
// Office doesn't handle exceptions rethrown here very well, it cores,
|
||
// all we can is log them and then set fail for the dispatch event!
|
||
// (if there is a listener of course)
|
||
catch ( const Exception & e )
|
||
{
|
||
aException = ::cppu::getCaughtException();
|
||
|
||
invokeResult <<= "ScriptProtocolHandler::dispatch: caught "
|
||
+ aException.getValueTypeName() + ": " + e.Message;
|
||
|
||
bCaughtException = true;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
invokeResult <<= u"ScriptProtocolHandler::dispatchWithNotification failed, ScriptProtocolHandler not initialised"_ustr;
|
||
}
|
||
|
||
if ( bCaughtException )
|
||
{
|
||
SfxAbstractDialogFactory* pFact = SfxAbstractDialogFactory::Create();
|
||
pFact->ShowAsyncScriptErrorDialog( nullptr, aException );
|
||
}
|
||
|
||
if ( !xListener.is() )
|
||
return;
|
||
|
||
// always call dispatchFinished(), because we didn't load a document but
|
||
// executed a macro instead!
|
||
css::frame::DispatchResultEvent aEvent;
|
||
|
||
aEvent.Source = getXWeak();
|
||
aEvent.Result = std::move(invokeResult);
|
||
aEvent.State = aState;
|
||
|
||
try
|
||
{
|
||
xListener->dispatchFinished( aEvent ) ;
|
||
}
|
||
catch(const RuntimeException &)
|
||
{
|
||
TOOLS_WARN_EXCEPTION("scripting",
|
||
"ScriptProtocolHandler::dispatchWithNotification: caught RuntimeException"
|
||
"while dispatchFinished" );
|
||
}
|
||
}
|
||
|
||
void SAL_CALL ScriptProtocolHandler::dispatch(
|
||
const URL& aURL, const Sequence< PropertyValue >& lArgs )
|
||
{
|
||
dispatchWithNotification( aURL, lArgs, Reference< XDispatchResultListener >() );
|
||
}
|
||
|
||
void SAL_CALL ScriptProtocolHandler::addStatusListener(
|
||
const Reference< XStatusListener >&, const URL& )
|
||
{
|
||
// implement if status is supported
|
||
}
|
||
|
||
void SAL_CALL ScriptProtocolHandler::removeStatusListener(
|
||
const Reference< XStatusListener >&, const URL& )
|
||
{}
|
||
|
||
bool
|
||
ScriptProtocolHandler::getScriptInvocation()
|
||
{
|
||
if ( !m_xScriptInvocation.is() && m_xFrame.is() )
|
||
{
|
||
Reference< XController > xController = m_xFrame->getController();
|
||
if ( xController .is() )
|
||
{
|
||
// try to obtain an XScriptInvocationContext interface, preferred from the
|
||
// mode, then from the controller
|
||
if ( !m_xScriptInvocation.set( xController->getModel(), UNO_QUERY ) )
|
||
m_xScriptInvocation.set( xController, UNO_QUERY );
|
||
}
|
||
else
|
||
{
|
||
if ( m_xFrame.is() )
|
||
{
|
||
SfxFrame* pFrame = nullptr;
|
||
for ( pFrame = SfxFrame::GetFirst(); pFrame; pFrame = SfxFrame::GetNext( *pFrame ) )
|
||
{
|
||
if ( pFrame->GetFrameInterface() == m_xFrame )
|
||
break;
|
||
}
|
||
if (SfxObjectShell* pDocShell = pFrame ? pFrame->GetCurrentDocument() : SfxObjectShell::Current())
|
||
{
|
||
Reference< XModel > xModel( pDocShell->GetModel() );
|
||
m_xScriptInvocation.set( xModel, UNO_QUERY );
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return m_xScriptInvocation.is();
|
||
}
|
||
|
||
void ScriptProtocolHandler::createScriptProvider()
|
||
{
|
||
if ( m_xScriptProvider.is() )
|
||
return;
|
||
|
||
try
|
||
{
|
||
// first, ask the component supporting the XScriptInvocationContext interface
|
||
// (if there is one) for a script provider
|
||
if ( getScriptInvocation() )
|
||
{
|
||
Reference< XScriptProviderSupplier > xSPS( m_xScriptInvocation, UNO_QUERY );
|
||
if ( xSPS.is() )
|
||
m_xScriptProvider = xSPS->getScriptProvider();
|
||
}
|
||
|
||
// second, ask the model in our frame
|
||
if ( !m_xScriptProvider.is() && m_xFrame.is() )
|
||
{
|
||
Reference< XController > xController = m_xFrame->getController();
|
||
if ( xController .is() )
|
||
{
|
||
Reference< XScriptProviderSupplier > xSPS( xController->getModel(), UNO_QUERY );
|
||
if ( xSPS.is() )
|
||
m_xScriptProvider = xSPS->getScriptProvider();
|
||
}
|
||
}
|
||
|
||
|
||
// as a fallback, ask the controller
|
||
if ( !m_xScriptProvider.is() && m_xFrame.is() )
|
||
{
|
||
Reference< XScriptProviderSupplier > xSPS( m_xFrame->getController(), UNO_QUERY );
|
||
if ( xSPS.is() )
|
||
m_xScriptProvider = xSPS->getScriptProvider();
|
||
}
|
||
|
||
// if nothing of this is successful, use the master script provider
|
||
if ( !m_xScriptProvider.is() )
|
||
{
|
||
Reference< provider::XScriptProviderFactory > xFac =
|
||
provider::theMasterScriptProviderFactory::get( m_xContext );
|
||
|
||
Any aContext;
|
||
if ( getScriptInvocation() )
|
||
aContext <<= m_xScriptInvocation;
|
||
m_xScriptProvider.set( xFac->createScriptProvider( aContext ), UNO_SET_THROW );
|
||
}
|
||
}
|
||
catch ( const Exception & e )
|
||
{
|
||
css::uno::Any anyEx = cppu::getCaughtException();
|
||
throw css::lang::WrappedTargetRuntimeException(
|
||
"ScriptProtocolHandler::createScriptProvider: " + e.Message,
|
||
nullptr, anyEx );
|
||
}
|
||
}
|
||
|
||
ScriptProtocolHandler::ScriptProtocolHandler( const Reference< css::uno::XComponentContext > & xContext )
|
||
: m_bInitialised( false ), m_xContext( xContext )
|
||
{
|
||
}
|
||
|
||
ScriptProtocolHandler::~ScriptProtocolHandler()
|
||
{
|
||
}
|
||
|
||
/* XServiceInfo */
|
||
OUString SAL_CALL ScriptProtocolHandler::getImplementationName( )
|
||
{
|
||
return u"com.sun.star.comp.ScriptProtocolHandler"_ustr;
|
||
}
|
||
|
||
/* XServiceInfo */
|
||
sal_Bool SAL_CALL ScriptProtocolHandler::supportsService(const OUString& sServiceName )
|
||
{
|
||
return cppu::supportsService(this, sServiceName);
|
||
}
|
||
|
||
/* XServiceInfo */
|
||
Sequence< OUString > SAL_CALL ScriptProtocolHandler::getSupportedServiceNames()
|
||
{
|
||
return {u"com.sun.star.frame.ProtocolHandler"_ustr};
|
||
}
|
||
|
||
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
|
||
scripting_ScriptProtocolHandler_get_implementation(
|
||
css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
|
||
{
|
||
return cppu::acquire(new ScriptProtocolHandler(context));
|
||
}
|
||
|
||
} // namespace scripting_protocolhandler
|
||
|
||
|
||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|