Coverage Report

Created: 2025-12-31 10:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sfx2/source/notify/eventsupplier.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <com/sun/star/beans/PropertyValue.hpp>
21
22
#include <com/sun/star/util/URL.hpp>
23
#include <com/sun/star/frame/Desktop.hpp>
24
#include <com/sun/star/uno/Sequence.hxx>
25
#include <com/sun/star/util/URLTransformer.hpp>
26
#include <com/sun/star/util/XURLTransformer.hpp>
27
#include <tools/urlobj.hxx>
28
#include <svl/macitem.hxx>
29
#include <sfx2/objsh.hxx>
30
#include <sfx2/evntconf.hxx>
31
#include <unotools/eventcfg.hxx>
32
#include <sal/log.hxx>
33
34
#include <comphelper/processfactory.hxx>
35
#include <comphelper/namedvaluecollection.hxx>
36
#include <comphelper/sequence.hxx>
37
#include <officecfg/Office/Common.hxx>
38
#include <eventsupplier.hxx>
39
40
#include <sfx2/app.hxx>
41
42
#include <sfx2/viewfrm.hxx>
43
#include <sfx2/frame.hxx>
44
#include <macroloader.hxx>
45
46
#include <unicode/errorcode.h>
47
#include <unicode/regex.h>
48
#include <unicode/unistr.h>
49
50
using namespace css;
51
using namespace ::com::sun::star;
52
53
54
55
    //  --- XNameReplace ---
56
57
void SAL_CALL SfxEvents_Impl::replaceByName( const OUString & aName, const uno::Any & rElement )
58
17
{
59
17
    std::unique_lock aGuard( maMutex );
60
61
    // find the event in the list and replace the data
62
17
    auto nIndex = comphelper::findValue(maEventNames, aName);
63
17
    if (nIndex == -1)
64
0
        throw container::NoSuchElementException();
65
66
    // check for correct type of the element
67
17
    if ( !::comphelper::NamedValueCollection::canExtractFrom( rElement ) )
68
0
        throw lang::IllegalArgumentException();
69
17
    ::comphelper::NamedValueCollection const aEventDescriptor( rElement );
70
71
    // create Configuration at first, creation might call this method also and that would overwrite everything
72
    // we might have stored before!
73
17
    if ( mpObjShell && !mpObjShell->IsLoading() )
74
0
    {
75
        // SetModified will end up calling into our documentEventOccured method
76
0
        aGuard.unlock();
77
0
        mpObjShell->SetModified();
78
0
        aGuard.lock();
79
0
    }
80
81
17
    ::comphelper::NamedValueCollection aNormalizedDescriptor;
82
17
    NormalizeMacro( aEventDescriptor, aNormalizedDescriptor, mpObjShell );
83
84
17
    OUString sType;
85
17
    if  (   ( aNormalizedDescriptor.size() == 1 )
86
0
         &&  !aNormalizedDescriptor.has( PROP_EVENT_TYPE ) //TODO
87
0
         &&  ( aNormalizedDescriptor.get( PROP_EVENT_TYPE ) >>= sType )
88
0
         &&  ( sType.isEmpty() )
89
17
        )
90
0
    {
91
        // An empty event type means no binding. Therefore reset data
92
        // to reflect that state.
93
        // (that's for compatibility only. Nowadays, the Tools/Customize dialog should
94
        // set an empty sequence to indicate the request for resetting the assignment.)
95
0
        OSL_ENSURE( false, "legacy event assignment format detected" );
96
0
        aNormalizedDescriptor.clear();
97
0
    }
98
99
17
    if ( !aNormalizedDescriptor.empty() )
100
17
    {
101
17
        maEventData[nIndex] = aNormalizedDescriptor.getPropertyValues();
102
17
    }
103
0
    else
104
0
    {
105
0
        maEventData[nIndex] = {};
106
0
    }
107
17
}
108
109
110
//  --- XNameAccess ---
111
112
uno::Any SAL_CALL SfxEvents_Impl::getByName( const OUString& aName )
113
168
{
114
168
    std::unique_lock aGuard( maMutex );
115
116
    // find the event in the list and return the data
117
118
168
    auto nIndex = comphelper::findValue(maEventNames, aName);
119
168
    if (nIndex != -1)
120
168
        return uno::Any(maEventData[nIndex]);
121
122
0
    throw container::NoSuchElementException();
123
168
}
124
125
126
uno::Sequence< OUString > SAL_CALL SfxEvents_Impl::getElementNames()
127
6
{
128
6
    return maEventNames;
129
6
}
130
131
132
sal_Bool SAL_CALL SfxEvents_Impl::hasByName( const OUString& aName )
133
17
{
134
17
    std::unique_lock aGuard( maMutex );
135
136
    // find the event in the list and return the data
137
138
17
    return comphelper::findValue(maEventNames, aName) != -1;
139
17
}
140
141
142
//  --- XElementAccess ( parent of XNameAccess ) ---
143
144
uno::Type SAL_CALL SfxEvents_Impl::getElementType()
145
0
{
146
0
    uno::Type aElementType = cppu::UnoType<uno::Sequence < beans::PropertyValue >>::get();
147
0
    return aElementType;
148
0
}
149
150
151
sal_Bool SAL_CALL SfxEvents_Impl::hasElements()
152
0
{
153
0
    std::unique_lock aGuard( maMutex );
154
155
0
    return maEventNames.hasElements();
156
0
}
157
158
bool SfxEvents_Impl::isScriptURLAllowed(const OUString& aScriptURL)
159
0
{
160
0
    std::optional<css::uno::Sequence<OUString>> allowedEvents(
161
0
        officecfg::Office::Common::Security::Scripting::AllowedDocumentEventURLs::get());
162
    // When AllowedDocumentEventURLs is empty, all event URLs are allowed
163
0
    if (!allowedEvents)
164
0
        return true;
165
166
0
    icu::ErrorCode status;
167
0
    const uint32_t rMatcherFlags = UREGEX_CASE_INSENSITIVE;
168
0
    icu::UnicodeString usInput(aScriptURL.getStr());
169
0
    const css::uno::Sequence<OUString>& rAllowedEvents = *allowedEvents;
170
0
    for (auto const& allowedEvent : rAllowedEvents)
171
0
    {
172
0
        icu::UnicodeString usRegex(allowedEvent.getStr());
173
0
        icu::RegexMatcher rmatch1(usRegex, usInput, rMatcherFlags, status);
174
0
        if (aScriptURL.startsWith(allowedEvent) || rmatch1.matches(status))
175
0
        {
176
0
            return true;
177
0
        }
178
0
    }
179
180
0
    return false;
181
0
}
182
183
void SfxEvents_Impl::Execute( css::uno::Sequence < css::beans::PropertyValue > const & aProperties, const document::DocumentEvent& aTrigger, SfxObjectShell* pDoc )
184
0
{
185
0
    OUString aType;
186
0
    OUString aScript;
187
0
    OUString aLibrary;
188
0
    OUString aMacroName;
189
190
0
    if ( !aProperties.hasElements() )
191
0
        return;
192
193
0
    for (const auto& rProp : aProperties)
194
0
    {
195
0
        if ( rProp.Name == PROP_EVENT_TYPE )
196
0
            rProp.Value >>= aType;
197
0
        else if ( rProp.Name == PROP_SCRIPT )
198
0
            rProp.Value >>= aScript;
199
0
        else if ( rProp.Name == PROP_LIBRARY )
200
0
            rProp.Value >>= aLibrary;
201
0
        else if ( rProp.Name == PROP_MACRO_NAME )
202
0
            rProp.Value >>= aMacroName;
203
0
        else {
204
0
            OSL_FAIL("Unknown property value!");
205
0
        }
206
0
    }
207
208
0
    if (aType.isEmpty())
209
0
    {
210
        // Empty type means no active binding for the event. Just ignore do nothing.
211
0
        return;
212
0
    }
213
214
0
    if (aScript.isEmpty())
215
0
        return;
216
217
0
    if (!isScriptURLAllowed(aScript))
218
0
        return;
219
220
0
    if (!pDoc)
221
0
        pDoc = SfxObjectShell::Current();
222
223
0
    if (pDoc && !SfxObjectShell::isScriptAccessAllowed(pDoc->GetModel()))
224
0
        return;
225
226
0
    if (aType == STAR_BASIC)
227
0
    {
228
0
        uno::Any aAny;
229
0
        SfxMacroLoader::loadMacro( aScript, aAny, pDoc );
230
0
    }
231
0
    else if (aType == "Service" || aType == "Script")
232
0
    {
233
0
        util::URL aURL;
234
0
        uno::Reference < util::XURLTransformer > xTrans( util::URLTransformer::create( ::comphelper::getProcessComponentContext() ) );
235
236
0
        aURL.Complete = aScript;
237
0
        xTrans->parseStrict( aURL );
238
239
0
        bool bAllowed = !SfxObjectShell::UnTrustedScript(aURL.Complete);
240
241
0
        if (bAllowed)
242
0
        {
243
0
            SfxViewFrame* pView = SfxViewFrame::GetFirst(pDoc);
244
245
0
            uno::Reference
246
0
                < frame::XDispatchProvider > xProv;
247
248
0
            if ( pView != nullptr )
249
0
            {
250
0
                xProv = uno::Reference
251
0
                    < frame::XDispatchProvider > (
252
0
                        pView->GetFrame().GetFrameInterface(), uno::UNO_QUERY );
253
0
            }
254
0
            else
255
0
            {
256
0
                xProv = frame::Desktop::create( ::comphelper::getProcessComponentContext() );
257
0
            }
258
259
0
            uno::Reference < frame::XDispatch > xDisp;
260
0
            if ( xProv.is() )
261
0
                xDisp = xProv->queryDispatch( aURL, OUString(), 0 );
262
263
0
            if ( xDisp.is() )
264
0
            {
265
0
                beans::PropertyValue aEventParam;
266
0
                aEventParam.Value <<= aTrigger;
267
0
                uno::Sequence< beans::PropertyValue > aDispatchArgs( &aEventParam, 1 );
268
0
                xDisp->dispatch( aURL, aDispatchArgs );
269
0
            }
270
0
        }
271
0
    }
272
0
    else
273
0
    {
274
0
        SAL_WARN( "sfx.notify", "notifyEvent(): Unsupported event type" );
275
0
    }
276
0
}
277
278
279
// --- ::document::XEventListener ---
280
281
void SAL_CALL SfxEvents_Impl::documentEventOccured( const document::DocumentEvent& aEvent )
282
0
{
283
0
    std::unique_lock aGuard( maMutex );
284
285
    // get the event name, find the corresponding data, execute the data
286
287
0
    auto nIndex = comphelper::findValue(maEventNames, aEvent.EventName);
288
0
    if ( nIndex == -1 )
289
0
        return;
290
291
0
    css::uno::Sequence < css::beans::PropertyValue > aEventData = maEventData[ nIndex ];
292
0
    aGuard.unlock();
293
0
    Execute( aEventData, aEvent, mpObjShell );
294
0
}
295
296
297
// --- ::lang::XEventListener ---
298
299
void SAL_CALL SfxEvents_Impl::disposing( const lang::EventObject& /*Source*/ )
300
101
{
301
101
    std::unique_lock aGuard( maMutex );
302
303
101
    if ( mxBroadcaster.is() )
304
101
    {
305
101
        mxBroadcaster->removeDocumentEventListener( this );
306
101
        mxBroadcaster = nullptr;
307
101
    }
308
101
}
309
310
311
SfxEvents_Impl::SfxEvents_Impl( SfxObjectShell* pShell,
312
                                uno::Reference< document::XDocumentEventBroadcaster > const & xBroadcaster )
313
101
{
314
    // get the list of supported events and store it
315
101
    if ( pShell )
316
101
        maEventNames = pShell->GetEventNames();
317
0
    else
318
0
        maEventNames = rtl::Reference<GlobalEventConfig>(new GlobalEventConfig)->getElementNames();
319
320
101
    maEventData.resize( maEventNames.getLength() );
321
322
101
    mpObjShell      = pShell;
323
101
    mxBroadcaster   = xBroadcaster;
324
325
101
    if ( mxBroadcaster.is() )
326
101
        mxBroadcaster->addDocumentEventListener( this );
327
101
}
328
329
330
SfxEvents_Impl::~SfxEvents_Impl()
331
101
{
332
101
}
333
334
335
std::unique_ptr<SvxMacro> SfxEvents_Impl::ConvertToMacro( const uno::Any& rElement, SfxObjectShell* pObjShell )
336
0
{
337
0
    std::unique_ptr<SvxMacro> pMacro;
338
0
    uno::Sequence < beans::PropertyValue > aProperties;
339
0
    uno::Any aAny;
340
0
    NormalizeMacro( rElement, aAny, pObjShell );
341
342
0
    if ( aAny >>= aProperties )
343
0
    {
344
0
        OUString aType;
345
0
        OUString aScriptURL;
346
0
        OUString aLibrary;
347
0
        OUString aMacroName;
348
349
0
        if ( !aProperties.hasElements() )
350
0
            return pMacro;
351
352
0
        for (const auto& rProp : aProperties)
353
0
        {
354
0
            if ( rProp.Name == PROP_EVENT_TYPE )
355
0
                rProp.Value >>= aType;
356
0
            else if ( rProp.Name == PROP_SCRIPT )
357
0
                rProp.Value >>= aScriptURL;
358
0
            else if ( rProp.Name == PROP_LIBRARY )
359
0
                rProp.Value >>= aLibrary;
360
0
            else if ( rProp.Name == PROP_MACRO_NAME )
361
0
                rProp.Value >>= aMacroName;
362
0
            else {
363
0
                OSL_FAIL("Unknown property value!");
364
0
            }
365
0
        }
366
367
        // Get the type
368
0
        ScriptType  eType( STARBASIC );
369
0
        if ( aType == STAR_BASIC )
370
0
            eType = STARBASIC;
371
0
        else if (aType == "Script" && !aScriptURL.isEmpty())
372
0
            eType = EXTENDED_STYPE;
373
0
        else if ( aType == SVX_MACRO_LANGUAGE_JAVASCRIPT )
374
0
            eType = JAVASCRIPT;
375
0
        else {
376
0
            SAL_WARN( "sfx.notify", "ConvertToMacro: Unknown macro type" );
377
0
        }
378
379
0
        if ( !aMacroName.isEmpty() )
380
0
        {
381
0
            if ( aLibrary == "application" )
382
0
                aLibrary = SfxGetpApp()->GetName();
383
0
            else
384
0
                aLibrary.clear();
385
0
            pMacro.reset(new SvxMacro( aMacroName, aLibrary, eType ));
386
0
        }
387
0
        else if ( eType == EXTENDED_STYPE )
388
0
            pMacro.reset(new SvxMacro( aScriptURL, aType ));
389
0
    }
390
391
0
    return pMacro;
392
0
}
393
394
void SfxEvents_Impl::NormalizeMacro( const uno::Any& rEvent, uno::Any& rRet, SfxObjectShell* pDoc )
395
0
{
396
0
    const ::comphelper::NamedValueCollection aEventDescriptor( rEvent );
397
0
    ::comphelper::NamedValueCollection aEventDescriptorOut;
398
399
0
    NormalizeMacro( aEventDescriptor, aEventDescriptorOut, pDoc );
400
401
0
    rRet <<= aEventDescriptorOut.getPropertyValues();
402
0
}
403
404
void SfxEvents_Impl::NormalizeMacro( const ::comphelper::NamedValueCollection& i_eventDescriptor,
405
        ::comphelper::NamedValueCollection& o_normalizedDescriptor, SfxObjectShell* i_document )
406
17
{
407
17
    SfxObjectShell* pDoc = i_document;
408
17
    if ( !pDoc )
409
0
        pDoc = SfxObjectShell::Current();
410
411
17
    OUString aType = i_eventDescriptor.getOrDefault( PROP_EVENT_TYPE, OUString() );
412
17
    OUString aScript = i_eventDescriptor.getOrDefault( PROP_SCRIPT, OUString() );
413
17
    OUString aLibrary = i_eventDescriptor.getOrDefault( PROP_LIBRARY, OUString() );
414
17
    OUString aMacroName = i_eventDescriptor.getOrDefault( PROP_MACRO_NAME, OUString() );
415
416
17
    if ( !aType.isEmpty() )
417
17
        o_normalizedDescriptor.put( PROP_EVENT_TYPE, aType );
418
17
    if ( !aScript.isEmpty() )
419
17
        o_normalizedDescriptor.put( PROP_SCRIPT, aScript );
420
421
17
    if ( aType != STAR_BASIC )
422
17
        return;
423
424
0
    if ( !aScript.isEmpty() )
425
0
    {
426
0
        if ( aMacroName.isEmpty() || aLibrary.isEmpty() )
427
0
        {
428
0
            sal_Int32 nThirdSlashPos = aScript.indexOf( '/', 8 );
429
0
            sal_Int32 nArgsPos = aScript.indexOf( '(' );
430
0
            if ( ( nThirdSlashPos != -1 ) && ( nArgsPos == -1 || nThirdSlashPos < nArgsPos ) )
431
0
            {
432
0
                OUString aBasMgrName( INetURLObject::decode( aScript.subView( 8, nThirdSlashPos-8 ), INetURLObject::DecodeMechanism::WithCharset ) );
433
0
                if (pDoc && aBasMgrName == ".")
434
0
                    aLibrary = pDoc->GetTitle();
435
0
                else
436
0
                    aLibrary = SfxGetpApp()->GetName();
437
438
                // Get the macro name
439
0
                aMacroName = aScript.copy( nThirdSlashPos+1, nArgsPos - nThirdSlashPos - 1 );
440
0
            }
441
0
            else
442
0
            {
443
0
                SAL_WARN( "sfx.notify", "ConvertToMacro: Unknown macro url format" );
444
0
            }
445
0
        }
446
0
    }
447
0
    else if ( !aMacroName.isEmpty() )
448
0
    {
449
0
        aScript = "macro://";
450
0
        if ( aLibrary != SfxGetpApp()->GetName() && aLibrary != "StarDesktop" && aLibrary != "application" )
451
0
            aScript += ".";
452
0
        aScript += "/" + aMacroName + "()";
453
0
    }
454
0
    else
455
        // wrong properties
456
0
        return;
457
458
0
    if (aLibrary != "document")
459
0
    {
460
0
        if ( aLibrary.isEmpty() || (pDoc && ( aLibrary == pDoc->GetTitle( SFX_TITLE_APINAME ) || aLibrary == pDoc->GetTitle() )) )
461
0
            aLibrary = "document";
462
0
        else
463
0
            aLibrary = "application";
464
0
    }
465
466
0
    o_normalizedDescriptor.put( PROP_SCRIPT, aScript );
467
0
    o_normalizedDescriptor.put( PROP_LIBRARY, aLibrary );
468
0
    o_normalizedDescriptor.put( PROP_MACRO_NAME, aMacroName );
469
0
}
470
471
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */