Coverage Report

Created: 2026-03-31 11:00

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