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