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 <tools/debug.hxx>
37 #include <tools/urlobj.hxx>
38 #include <tools/diagnose_ex.h>
39 #include <unotools/pathoptions.hxx>
40 #include <vcl/svapp.hxx>
41 
42 #include <sfx2/app.hxx>
43 #include <appdata.hxx>
44 #include <sfx2/objsh.hxx>
45 #include <sfx2/viewfrm.hxx>
46 #include <sfx2/dispatch.hxx>
47 #include <sfx2/sfxsids.hrc>
48 #include <sfx2/docfile.hxx>
49 #include <ucbhelper/content.hxx>
50 #include <comphelper/processfactory.hxx>
51 
52 #if defined(_WIN32)
53 
SfxDdeServiceName_Impl(const OUString & sIn)54 static OUString SfxDdeServiceName_Impl( const OUString& sIn )
55 {
56     OUStringBuffer sReturn(sIn.getLength());
57 
58     for ( sal_uInt16 n = sIn.getLength(); n; --n )
59     {
60         sal_Unicode cChar = sIn[n-1];
61         if (rtl::isAsciiAlphanumeric(cChar))
62             sReturn.append(cChar);
63     }
64 
65     return sReturn.makeStringAndClear();
66 }
67 
68 namespace {
69 
70 class ImplDdeService : public DdeService
71 {
72 public:
ImplDdeService(const OUString & rNm)73     explicit ImplDdeService( const OUString& rNm )
74         : DdeService( rNm )
75     {}
76     virtual bool MakeTopic( const OUString& );
77 
78     virtual OUString  Topics();
79 
80     virtual bool SysTopicExecute( const OUString* pStr );
81 };
82 
lcl_IsDocument(const OUString & rContent)83     bool lcl_IsDocument( const OUString& rContent )
84     {
85         using namespace com::sun::star;
86 
87         bool bRet = false;
88         INetURLObject aObj( rContent );
89         DBG_ASSERT( aObj.GetProtocol() != INetProtocol::NotValid, "Invalid URL!" );
90 
91         try
92         {
93             ::ucbhelper::Content aCnt( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), uno::Reference< ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() );
94             bRet = aCnt.isDocument();
95         }
96         catch( const uno::Exception& )
97         {
98             TOOLS_WARN_EXCEPTION( "sfx.appl", "" );
99         }
100 
101         return bRet;
102     }
103 }
104 
MakeTopic(const OUString & rNm)105 bool ImplDdeService::MakeTopic( const OUString& rNm )
106 {
107     // Workaround for Event after Main() under OS/2
108     // happens when exiting starts the App again
109     if ( !Application::IsInExecute() )
110         return false;
111 
112     // The Topic rNm is sought, do we have it?
113     // First only loop over the ObjectShells to find those
114     // with the specific name:
115     bool bRet = false;
116     OUString sNm( rNm.toAsciiLowerCase() );
117     SfxObjectShell* pShell = SfxObjectShell::GetFirst();
118     while( pShell )
119     {
120         OUString sTmp( pShell->GetTitle(SFX_TITLE_FULLNAME) );
121         if( sNm == sTmp.toAsciiLowerCase() )
122         {
123             SfxGetpApp()->AddDdeTopic( pShell );
124             bRet = true;
125             break;
126         }
127         pShell = SfxObjectShell::GetNext( *pShell );
128     }
129 
130     if( !bRet )
131     {
132         bool abs;
133         OUString url;
134         try {
135             url = rtl::Uri::convertRelToAbs(SvtPathOptions().GetWorkPath(), rNm);
136             abs = true;
137         } catch (rtl::MalformedUriException &) {
138             abs = false;
139         }
140         if ( abs && lcl_IsDocument( url ) )
141         {
142             // File exists? then try to load it:
143             SfxStringItem aName( SID_FILE_NAME, url );
144             SfxBoolItem aNewView(SID_OPEN_NEW_VIEW, true);
145 
146             SfxBoolItem aSilent(SID_SILENT, true);
147             SfxDispatcher* pDispatcher = SfxGetpApp()->GetDispatcher_Impl();
148             const SfxPoolItem* pRet = pDispatcher->ExecuteList(SID_OPENDOC,
149                     SfxCallMode::SYNCHRON,
150                     { &aName, &aNewView, &aSilent });
151 
152             if( auto const item = dynamic_cast< const SfxViewFrameItem *>( pRet );
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 
Topics()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 
SysTopicExecute(const OUString * pStr)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 
SfxDdeDocTopic_Impl(SfxObjectShell * pShell)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 */
SfxAppEvent_Impl(const OUString & rCmd,std::u16string_view rEvent,ApplicationEvent::Type eType)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, 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 */
DdeExecute(const OUString & rCmd)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 */
DdeExecute(const OUString & rCmd)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 */
DdeGetData(const OUString &,const OUString &,css::uno::Any &)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 */
DdeSetData(const OUString &,const OUString &,const css::uno::Any &)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 */
DdeCreateLinkSource(const OUString &)385 ::sfx2::SvLinkSource* SfxObjectShell::DdeCreateLinkSource( const OUString& ) // the Item to be addressed
386 {
387     return nullptr;
388 }
389 
ReconnectDdeLink(SfxObjectShell &)390 void SfxObjectShell::ReconnectDdeLink(SfxObjectShell& /*rServer*/)
391 {
392 }
393 
ReconnectDdeLinks(SfxObjectShell & rServer)394 void SfxObjectShell::ReconnectDdeLinks(SfxObjectShell& rServer)
395 {
396     SfxObjectShell* p = GetFirst(nullptr, false);
397     while (p)
398     {
399         if (&rServer != p)
400             p->ReconnectDdeLink(rServer);
401 
402         p = GetNext(*p, nullptr, false);
403     }
404 }
405 
InitializeDde()406 bool SfxApplication::InitializeDde()
407 {
408     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         pImpl->pDocTopics.reset(new SfxDdeDocTopics_Impl);
418 
419         // we certainly want to support RTF!
420         pImpl->pDdeService->AddFormat( SotClipboardFormatId::RTF );
421         pImpl->pDdeService->AddFormat( SotClipboardFormatId::RICHTEXT );
422 
423         // Config path as a topic because of multiple starts
424         INetURLObject aOfficeLockFile( SvtPathOptions().GetUserConfigPath() );
425         aOfficeLockFile.insertName( u"soffice.lck" );
426         OUString aService( SfxDdeServiceName_Impl(
427                     aOfficeLockFile.GetMainURL(INetURLObject::DecodeMechanism::ToIUri) ) );
428         aService = aService.toAsciiUpperCase();
429         pImpl->pDdeService2.reset( new ImplDdeService( aService ));
430         pImpl->pTriggerTopic.reset(new SfxDdeTriggerTopic_Impl);
431         pImpl->pDdeService2->AddTopic( *pImpl->pTriggerTopic );
432     }
433 #endif
434     return !nError;
435 }
436 
DeInitDDE()437 void SfxAppData_Impl::DeInitDDE()
438 {
439     pTriggerTopic.reset();
440     pDdeService2.reset();
441     pDocTopics.reset();
442     pDdeService.reset();
443 }
444 
445 #if defined(_WIN32)
AddDdeTopic(SfxObjectShell * pSh)446 void SfxApplication::AddDdeTopic( SfxObjectShell* pSh )
447 {
448     //OV: DDE is disconnected in server mode!
449     if( !pImpl->pDocTopics )
450         return;
451 
452     // prevent double submit
453     OUString sShellNm;
454     bool bFnd = false;
455     for (size_t n = pImpl->pDocTopics->size(); n;)
456     {
457         if( (*pImpl->pDocTopics)[ --n ]->pSh == pSh )
458         {
459             // If the document is untitled, is still a new Topic is created!
460             if( !bFnd )
461             {
462                 bFnd = true;
463                 sShellNm = pSh->GetTitle(SFX_TITLE_FULLNAME).toAsciiLowerCase();
464             }
465             OUString sNm( (*pImpl->pDocTopics)[ n ]->GetName() );
466             if( sShellNm == sNm.toAsciiLowerCase() )
467                 return ;
468         }
469     }
470 
471     SfxDdeDocTopic_Impl *const pTopic = new SfxDdeDocTopic_Impl(pSh);
472     pImpl->pDocTopics->push_back(pTopic);
473     pImpl->pDdeService->AddTopic( *pTopic );
474 }
475 #endif
476 
RemoveDdeTopic(SfxObjectShell const * pSh)477 void SfxApplication::RemoveDdeTopic( SfxObjectShell const * pSh )
478 {
479 #if defined(_WIN32)
480     //OV: DDE is disconnected in server mode!
481     if( !pImpl->pDocTopics )
482         return;
483 
484     for (size_t n = pImpl->pDocTopics->size(); n; )
485     {
486         SfxDdeDocTopic_Impl *const pTopic = (*pImpl->pDocTopics)[ --n ];
487         if (pTopic->pSh == pSh)
488         {
489             pImpl->pDdeService->RemoveTopic( *pTopic );
490             delete pTopic;
491             pImpl->pDocTopics->erase( pImpl->pDocTopics->begin() + n );
492         }
493     }
494 #else
495     (void) pSh;
496 #endif
497 }
498 
GetDdeService() const499 const DdeService* SfxApplication::GetDdeService() const
500 {
501     return pImpl->pDdeService.get();
502 }
503 
GetDdeService()504 DdeService* SfxApplication::GetDdeService()
505 {
506     return pImpl->pDdeService.get();
507 }
508 
509 #if defined(_WIN32)
510 
Get(SotClipboardFormatId nFormat)511 DdeData* SfxDdeDocTopic_Impl::Get(SotClipboardFormatId nFormat)
512 {
513     OUString sMimeType( SotExchange::GetFormatMimeType( nFormat ));
514     css::uno::Any aValue;
515     bool bRet = pSh->DdeGetData( GetCurItem(), sMimeType, aValue );
516     if( bRet && aValue.hasValue() && ( aValue >>= aSeq ) )
517     {
518         aData = DdeData( aSeq.getConstArray(), aSeq.getLength(), nFormat );
519         return &aData;
520     }
521     aSeq.realloc( 0 );
522     return nullptr;
523 }
524 
Put(const DdeData * pData)525 bool SfxDdeDocTopic_Impl::Put( const DdeData* pData )
526 {
527     aSeq = css::uno::Sequence< sal_Int8 >(
528                             static_cast<sal_Int8 const *>(pData->getData()), pData->getSize() );
529     bool bRet;
530     if( aSeq.getLength() )
531     {
532         css::uno::Any aValue;
533         aValue <<= aSeq;
534         OUString sMimeType( SotExchange::GetFormatMimeType( pData->GetFormat() ));
535         bRet = pSh->DdeSetData( GetCurItem(), sMimeType, aValue );
536     }
537     else
538         bRet = false;
539     return bRet;
540 }
541 
Execute(const OUString * pStr)542 bool SfxDdeDocTopic_Impl::Execute( const OUString* pStr )
543 {
544     return pStr && pSh->DdeExecute( *pStr );
545 }
546 
MakeItem(const OUString & rItem)547 bool SfxDdeDocTopic_Impl::MakeItem( const OUString& rItem )
548 {
549     AddItem( DdeItem( rItem ) );
550     return true;
551 }
552 
StartAdviseLoop()553 bool SfxDdeDocTopic_Impl::StartAdviseLoop()
554 {
555     bool bRet = false;
556     ::sfx2::SvLinkSource* pNewObj = pSh->DdeCreateLinkSource( GetCurItem() );
557     if( pNewObj )
558     {
559         // then we also establish a corresponding SvBaseLink
560         OUString sNm, sTmp( Application::GetAppName() );
561         ::sfx2::MakeLnkName( sNm, &sTmp, pSh->GetTitle(SFX_TITLE_FULLNAME), GetCurItem() );
562         new ::sfx2::SvBaseLink( sNm, sfx2::SvBaseLinkObjectType::DdeExternal, pNewObj );
563         bRet = true;
564     }
565     return bRet;
566 }
567 
568 #endif
569 
570 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
571