Coverage Report

Created: 2025-11-16 09:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sfx2/source/appl/appdde.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 <sal/config.h>
21
22
#include <string_view>
23
24
#include <config_features.h>
25
#include <rtl/character.hxx>
26
#include <rtl/malformeduriexception.hxx>
27
#include <rtl/uri.hxx>
28
#include <sot/exchange.hxx>
29
#include <svl/eitem.hxx>
30
#include <basic/sbstar.hxx>
31
#include <svl/stritem.hxx>
32
#include <svl/svdde.hxx>
33
#include <sfx2/lnkbase.hxx>
34
#include <sfx2/linkmgr.hxx>
35
36
#include <o3tl/test_info.hxx>
37
#include <tools/debug.hxx>
38
#include <tools/urlobj.hxx>
39
#include <comphelper/diagnose_ex.hxx>
40
#include <unotools/pathoptions.hxx>
41
#include <vcl/svapp.hxx>
42
43
#include <sfx2/app.hxx>
44
#include <appdata.hxx>
45
#include <sfx2/objsh.hxx>
46
#include <sfx2/viewfrm.hxx>
47
#include <sfx2/dispatch.hxx>
48
#include <sfx2/sfxsids.hrc>
49
#include <sfx2/docfile.hxx>
50
#include <ucbhelper/content.hxx>
51
#include <comphelper/processfactory.hxx>
52
53
#if defined(_WIN32)
54
55
static OUString SfxDdeServiceName_Impl( const OUString& sIn )
56
{
57
    OUStringBuffer sReturn(sIn.getLength());
58
59
    for ( sal_uInt16 n = sIn.getLength(); n; --n )
60
    {
61
        sal_Unicode cChar = sIn[n-1];
62
        if (rtl::isAsciiAlphanumeric(cChar))
63
            sReturn.append(cChar);
64
    }
65
66
    return sReturn.makeStringAndClear();
67
}
68
69
namespace {
70
71
class ImplDdeService : public DdeService
72
{
73
public:
74
    explicit ImplDdeService( const OUString& rNm )
75
        : DdeService( rNm )
76
    {}
77
    virtual bool MakeTopic( const OUString& ) override;
78
79
    virtual OUString  Topics();
80
81
    virtual bool SysTopicExecute( const OUString* pStr );
82
};
83
84
    bool lcl_IsDocument( std::u16string_view rContent )
85
    {
86
        using namespace com::sun::star;
87
88
        bool bRet = false;
89
        INetURLObject aObj( rContent );
90
        DBG_ASSERT( aObj.GetProtocol() != INetProtocol::NotValid, "Invalid URL!" );
91
92
        try
93
        {
94
            ::ucbhelper::Content aCnt( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), uno::Reference< ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() );
95
            bRet = aCnt.isDocument();
96
        }
97
        catch( const uno::Exception& )
98
        {
99
            TOOLS_WARN_EXCEPTION( "sfx.appl", "" );
100
        }
101
102
        return bRet;
103
    }
104
}
105
106
bool ImplDdeService::MakeTopic( const OUString& rNm )
107
{
108
    // Workaround for Event after Main() under OS/2
109
    // happens when exiting starts the App again
110
    if ( !Application::IsInExecute() && !o3tl::IsRunningUnitTest() )
111
        return false;
112
113
    // The Topic rNm is sought, do we have it?
114
    // First only loop over the ObjectShells to find those
115
    // with the specific name:
116
    bool bRet = false;
117
    OUString sNm( rNm.toAsciiLowerCase() );
118
    SfxObjectShell* pShell = SfxObjectShell::GetFirst();
119
    while( pShell )
120
    {
121
        OUString sTmp( pShell->GetTitle(SFX_TITLE_FULLNAME) );
122
        if( sNm == sTmp.toAsciiLowerCase() )
123
        {
124
            SfxGetpApp()->AddDdeTopic( pShell );
125
            bRet = true;
126
            break;
127
        }
128
        pShell = SfxObjectShell::GetNext( *pShell );
129
    }
130
131
    if( !bRet )
132
    {
133
        bool abs;
134
        OUString url;
135
        try {
136
            url = rtl::Uri::convertRelToAbs(SvtPathOptions().GetWorkPath(), rNm);
137
            abs = true;
138
        } catch (rtl::MalformedUriException &) {
139
            abs = false;
140
        }
141
        if ( abs && lcl_IsDocument( url ) )
142
        {
143
            // File exists? then try to load it:
144
            SfxStringItem aName( SID_FILE_NAME, url );
145
            SfxBoolItem aNewView(SID_OPEN_NEW_VIEW, true);
146
147
            SfxBoolItem aSilent(SID_SILENT, true);
148
            const SfxPoolItemHolder aResult(SfxGetpApp()->GetDispatcher_Impl()->ExecuteList(SID_OPENDOC,
149
                SfxCallMode::SYNCHRON,
150
                { &aName, &aNewView, &aSilent }));
151
152
            if( auto const item = dynamic_cast< const SfxViewFrameItem *>(aResult.getItem());
153
                item &&
154
                item->GetFrame() &&
155
                nullptr != ( pShell = item->GetFrame()->GetObjectShell() ) )
156
            {
157
                SfxGetpApp()->AddDdeTopic( pShell );
158
                bRet = true;
159
            }
160
        }
161
    }
162
    return bRet;
163
}
164
165
OUString ImplDdeService::Topics()
166
{
167
    OUString sRet;
168
    if( GetSysTopic() )
169
        sRet += GetSysTopic()->GetName();
170
171
    SfxObjectShell* pShell = SfxObjectShell::GetFirst();
172
    while( pShell )
173
    {
174
        if( SfxViewFrame::GetFirst( pShell ) )
175
        {
176
            if( !sRet.isEmpty() )
177
                sRet += "\t";
178
            sRet += pShell->GetTitle(SFX_TITLE_FULLNAME);
179
        }
180
        pShell = SfxObjectShell::GetNext( *pShell );
181
    }
182
    if( !sRet.isEmpty() )
183
        sRet += "\r\n";
184
    return sRet;
185
}
186
187
bool ImplDdeService::SysTopicExecute( const OUString* pStr )
188
{
189
    return SfxApplication::DdeExecute( *pStr );
190
}
191
#endif
192
193
class SfxDdeDocTopic_Impl : public DdeTopic
194
{
195
#if defined(_WIN32)
196
public:
197
    SfxObjectShell* pSh;
198
    DdeData aData;
199
    css::uno::Sequence< sal_Int8 > aSeq;
200
201
    explicit SfxDdeDocTopic_Impl( SfxObjectShell* pShell )
202
        : DdeTopic( pShell->GetTitle(SFX_TITLE_FULLNAME) ), pSh( pShell )
203
    {}
204
205
    virtual DdeData* Get( SotClipboardFormatId ) override;
206
    virtual bool Put( const DdeData* ) override;
207
    virtual bool Execute( const OUString* ) override;
208
    virtual bool StartAdviseLoop() override;
209
    virtual bool MakeItem( const OUString& rItem ) override;
210
#endif
211
};
212
213
214
#if defined(_WIN32)
215
216
namespace {
217
218
/*  [Description]
219
220
    Checks if 'rCmd' of the event 'rEvent' is (without '(') and then assemble
221
    this data into a <ApplicationEvent>, which is then executed through
222
    <Application::AppEvent()>. If 'rCmd' is the given event 'rEvent', then
223
    TRUE is returned, otherwise FALSE.
224
225
    [Example]
226
227
    rCmd = "Open(\"d:\doc\doc.sdw\")"
228
    rEvent = "Open"
229
*/
230
bool SfxAppEvent_Impl( const OUString& rCmd, std::u16string_view rEvent,
231
                           ApplicationEvent::Type eType )
232
{
233
    OUString sEvent(OUString::Concat(rEvent) + "(");
234
    if (rCmd.startsWithIgnoreAsciiCase(sEvent))
235
    {
236
        sal_Int32 start = sEvent.getLength();
237
        if ( rCmd.getLength() - start >= 2 )
238
        {
239
            // Transform into the ApplicationEvent Format
240
            //TODO: I /assume/ that rCmd should match the syntax of
241
            // <http://msdn.microsoft.com/en-us/library/ms648995.aspx>
242
            // "WM_DDE_EXECUTE message" but does not (handle commands enclosed
243
            // in [...]; handle commas separating multiple arguments; handle
244
            // double "", ((, )), [[, ]] in quoted arguments); see also the mail
245
            // thread starting at <http://lists.freedesktop.org/archives/
246
            // libreoffice/2013-July/054779.html> "DDE on Windows."
247
            std::vector<OUString> aData;
248
            for ( sal_Int32 n = start; n < rCmd.getLength() - 1; )
249
            {
250
                // Resiliently read arguments either starting with " and
251
                // spanning to the next " (if any; TODO: do we need to undo any
252
                // escaping within the string?) or with neither " nor SPC and
253
                // spanning to the next SPC (if any; TODO: is this from not
254
                // wrapped in "..." relevant? it would have been parsed by the
255
                // original code even if that was only by accident, so I left it
256
                // in), with runs of SPCs treated like single ones:
257
                switch ( rCmd[n] )
258
                {
259
                case '"':
260
                    {
261
                        sal_Int32 i = rCmd.indexOf('"', ++n);
262
                        if (i < 0 || i > rCmd.getLength() - 1) {
263
                            i = rCmd.getLength() - 1;
264
                        }
265
                        aData.push_back(rCmd.copy(n, i - n));
266
                        n = i + 1;
267
                        break;
268
                    }
269
                case ' ':
270
                    ++n;
271
                    break;
272
                default:
273
                    {
274
                        sal_Int32 i = rCmd.indexOf(' ', n);
275
                        if (i < 0 || i > rCmd.getLength() - 1) {
276
                            i = rCmd.getLength() - 1;
277
                        }
278
                        aData.push_back(rCmd.copy(n, i - n));
279
                        n = i + 1;
280
                        break;
281
                    }
282
                }
283
            }
284
285
            GetpApp()->AppEvent( ApplicationEvent(eType, std::move(aData)) );
286
            return true;
287
        }
288
    }
289
290
    return false;
291
}
292
293
}
294
295
/*  Description]
296
297
    This method can be overridden by application developers, to receive
298
    DDE-commands directed to their SfxApplication subclass.
299
300
    The base implementation understands the API functionality of the
301
    relevant SfxApplication subclass in BASIC syntax. Return values can
302
    not be transferred, unfortunately.
303
*/
304
bool SfxApplication::DdeExecute( const OUString&   rCmd )  // Expressed in our BASIC-Syntax
305
{
306
    // Print or Open-Event?
307
    if ( !( SfxAppEvent_Impl( rCmd, u"Print", ApplicationEvent::Type::Print ) ||
308
            SfxAppEvent_Impl( rCmd, u"Open", ApplicationEvent::Type::Open ) ) )
309
    {
310
        // all others are BASIC
311
        StarBASIC* pBasic = GetBasic();
312
        DBG_ASSERT( pBasic, "Where is the Basic???" );
313
        SbxVariable* pRet = pBasic->Execute( rCmd );
314
        if( !pRet )
315
        {
316
            SbxBase::ResetError();
317
            return false;
318
        }
319
    }
320
    return true;
321
}
322
323
/*  [Description]
324
325
    This method can be overridden by application developers, to receive
326
    DDE-commands directed to the their SfxApplication subclass.
327
328
    The base implementation does nothing and returns 0.
329
*/
330
bool SfxObjectShell::DdeExecute( const OUString&   rCmd )  // Expressed in our BASIC-Syntax
331
{
332
#if !HAVE_FEATURE_SCRIPTING
333
    (void) rCmd;
334
#else
335
    StarBASIC* pBasic = GetBasic();
336
    DBG_ASSERT( pBasic, "Where is the Basic???" ) ;
337
    SbxVariable* pRet = pBasic->Execute( rCmd );
338
    if( !pRet )
339
    {
340
        SbxBase::ResetError();
341
        return false;
342
    }
343
#endif
344
    return true;
345
}
346
347
/*  [Description]
348
349
    This method can be overridden by application developers, to receive
350
    DDE-data-requests directed to their SfxApplication subclass.
351
352
    The base implementation provides no data and returns false.
353
*/
354
bool SfxObjectShell::DdeGetData( const OUString&,              // the Item to be addressed
355
                                 const OUString&,              // in: Format
356
                                 css::uno::Any& )// out: requested data
357
{
358
    return false;
359
}
360
361
362
/*  [Description]
363
364
    This method can be overridden by application developers, to receive
365
    DDE-data directed to their SfxApplication subclass.
366
367
    The base implementation is not receiving any data and returns false.
368
*/
369
bool SfxObjectShell::DdeSetData( const OUString&,                    // the Item to be addressed
370
                                 const OUString&,                    // in: Format
371
                                 const css::uno::Any& )// out: requested data
372
{
373
    return false;
374
}
375
376
#endif
377
378
/*  [Description]
379
380
    This method can be overridden by application developers, to establish
381
    a DDE-hotlink to their SfxApplication subclass.
382
383
    The base implementation is not generate a link and returns 0.
384
*/
385
::sfx2::SvLinkSource* SfxObjectShell::DdeCreateLinkSource( const OUString& ) // the Item to be addressed
386
0
{
387
0
    return nullptr;
388
0
}
389
390
void SfxObjectShell::ReconnectDdeLink(SfxObjectShell& /*rServer*/)
391
0
{
392
0
}
393
394
void SfxObjectShell::ReconnectDdeLinks(SfxObjectShell& rServer)
395
0
{
396
0
    SfxObjectShell* p = GetFirst(nullptr, false);
397
0
    while (p)
398
0
    {
399
0
        if (&rServer != p)
400
0
            p->ReconnectDdeLink(rServer);
401
402
0
        p = GetNext(*p, nullptr, false);
403
0
    }
404
0
}
405
406
bool SfxApplication::InitializeDde()
407
26
{
408
26
    int nError = 0;
409
#if defined(_WIN32)
410
    DBG_ASSERT( !pImpl->pDdeService,
411
                "Dde can not be initialized multiple times" );
412
413
    pImpl->pDdeService.reset(new ImplDdeService( Application::GetAppName() ));
414
    nError = pImpl->pDdeService->GetError();
415
    if( !nError )
416
    {
417
        // we certainly want to support RTF!
418
        pImpl->pDdeService->AddFormat( SotClipboardFormatId::RTF );
419
        pImpl->pDdeService->AddFormat( SotClipboardFormatId::RICHTEXT );
420
421
        // Config path as a topic because of multiple starts
422
        INetURLObject aOfficeLockFile( SvtPathOptions().GetUserConfigPath() );
423
        aOfficeLockFile.insertName( u"soffice.lck" );
424
        OUString aService( SfxDdeServiceName_Impl(
425
                    aOfficeLockFile.GetMainURL(INetURLObject::DecodeMechanism::ToIUri) ) );
426
        aService = aService.toAsciiUpperCase();
427
        pImpl->pDdeService2.reset( new ImplDdeService( aService ));
428
        pImpl->pTriggerTopic.reset(new SfxDdeTriggerTopic_Impl);
429
        pImpl->pDdeService2->AddTopic( *pImpl->pTriggerTopic );
430
    }
431
#endif
432
26
    return !nError;
433
26
}
434
435
void SfxAppData_Impl::DeInitDDE()
436
0
{
437
0
    pTriggerTopic.reset();
438
0
    pDdeService2.reset();
439
0
    maDocTopics.clear();
440
0
    pDdeService.reset();
441
0
}
442
443
#if defined(_WIN32)
444
void SfxApplication::AddDdeTopic( SfxObjectShell* pSh )
445
{
446
    //OV: DDE is disconnected in server mode!
447
    if (!pImpl->pDdeService)
448
        return;
449
450
    // prevent double submit
451
    OUString sShellNm;
452
    bool bFnd = false;
453
    for (size_t n = pImpl->maDocTopics.size(); n;)
454
    {
455
        if( pImpl->maDocTopics[ --n ]->pSh == pSh )
456
        {
457
            // If the document is untitled, is still a new Topic is created!
458
            if( !bFnd )
459
            {
460
                bFnd = true;
461
                sShellNm = pSh->GetTitle(SFX_TITLE_FULLNAME).toAsciiLowerCase();
462
            }
463
            OUString sNm( pImpl->maDocTopics[ n ]->GetName() );
464
            if( sShellNm == sNm.toAsciiLowerCase() )
465
                return ;
466
        }
467
    }
468
469
    SfxDdeDocTopic_Impl *const pTopic = new SfxDdeDocTopic_Impl(pSh);
470
    pImpl->maDocTopics.push_back(pTopic);
471
    pImpl->pDdeService->AddTopic( *pTopic );
472
}
473
#endif
474
475
void SfxApplication::RemoveDdeTopic( SfxObjectShell const * pSh )
476
0
{
477
#if defined(_WIN32)
478
    //OV: DDE is disconnected in server mode!
479
    if( pImpl->maDocTopics.empty() )
480
        return;
481
482
    for (size_t n = pImpl->maDocTopics.size(); n; )
483
    {
484
        SfxDdeDocTopic_Impl *const pTopic = pImpl->maDocTopics[ --n ];
485
        if (pTopic->pSh == pSh)
486
        {
487
            pImpl->pDdeService->RemoveTopic( *pTopic );
488
            delete pTopic;
489
            pImpl->maDocTopics.erase( pImpl->maDocTopics.begin() + n );
490
        }
491
    }
492
#else
493
0
    (void) pSh;
494
0
#endif
495
0
}
496
497
const DdeService* SfxApplication::GetDdeService() const
498
0
{
499
0
    return pImpl->pDdeService.get();
500
0
}
501
502
DdeService* SfxApplication::GetDdeService()
503
261k
{
504
261k
    return pImpl->pDdeService.get();
505
261k
}
506
507
#if defined(_WIN32)
508
509
DdeData* SfxDdeDocTopic_Impl::Get(SotClipboardFormatId nFormat)
510
{
511
    OUString sMimeType( SotExchange::GetFormatMimeType( nFormat ));
512
    css::uno::Any aValue;
513
    bool bRet = pSh->DdeGetData( GetCurItem(), sMimeType, aValue );
514
    if( bRet && aValue.hasValue() && ( aValue >>= aSeq ) )
515
    {
516
        aData = DdeData( aSeq.getConstArray(), aSeq.getLength(), nFormat );
517
        return &aData;
518
    }
519
    aSeq.realloc( 0 );
520
    return nullptr;
521
}
522
523
bool SfxDdeDocTopic_Impl::Put( const DdeData* pData )
524
{
525
    if (pData->getSize())
526
    {
527
        css::uno::Any aValue;
528
        if (pData->GetFormat() == SotClipboardFormatId::STRING)
529
        {
530
            aValue <<= OUString(reinterpret_cast<const sal_Unicode*>(pData->getData()));
531
        }
532
        else
533
        {
534
            aValue <<= css::uno::Sequence(static_cast<sal_Int8 const*>(pData->getData()),
535
                                          pData->getSize());
536
        }
537
        return pSh->DdeSetData(GetCurItem(), SotExchange::GetFormatMimeType(pData->GetFormat()),
538
                               aValue);
539
    }
540
    return false;
541
}
542
543
bool SfxDdeDocTopic_Impl::Execute( const OUString* pStr )
544
{
545
    return pStr && pSh->DdeExecute( *pStr );
546
}
547
548
bool SfxDdeDocTopic_Impl::MakeItem( const OUString& rItem )
549
{
550
    AddItem( DdeItem( rItem ) );
551
    return true;
552
}
553
554
bool SfxDdeDocTopic_Impl::StartAdviseLoop()
555
{
556
    bool bRet = false;
557
    ::sfx2::SvLinkSource* pNewObj = pSh->DdeCreateLinkSource( GetCurItem() );
558
    if( pNewObj )
559
    {
560
        // then we also establish a corresponding SvBaseLink
561
        OUString sNm, sTmp( Application::GetAppName() );
562
        ::sfx2::MakeLnkName( sNm, &sTmp, pSh->GetTitle(SFX_TITLE_FULLNAME), GetCurItem() );
563
        new ::sfx2::SvBaseLink( sNm, sfx2::SvBaseLinkObjectType::DdeExternal, pNewObj );
564
        bRet = true;
565
    }
566
    return bRet;
567
}
568
569
#endif
570
571
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */