xref: /core/desktop/source/app/officeipcthread.cxx (revision 90eb9ea8)
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 <config_dbus.h>
23 #include <config_features.h>
24 #include <config_feature_desktop.h>
25 
26 #include <app.hxx>
27 #include "officeipcthread.hxx"
28 #include "cmdlineargs.hxx"
29 #include "dispatchwatcher.hxx"
30 #include <com/sun/star/frame/TerminationVetoException.hpp>
31 #include <salhelper/thread.hxx>
32 #include <sal/log.hxx>
33 #include <unotools/bootstrap.hxx>
34 #include <utility>
35 #include <vcl/svapp.hxx>
36 #include <unotools/configmgr.hxx>
37 #include <osl/pipe.hxx>
38 #include <rtl/digest.h>
39 #include <rtl/ustrbuf.hxx>
40 #include <osl/conditn.hxx>
41 #include <unotools/moduleoptions.hxx>
42 #include <rtl/strbuf.hxx>
43 #include <cppuhelper/supportsservice.hxx>
44 #include <osl/file.hxx>
45 #include <rtl/process.h>
46 #include <o3tl/string_view.hxx>
47 
48 #include <cassert>
49 #include <cstdlib>
50 #include <memory>
51 #include <thread>
52 
53 #if ENABLE_DBUS
54 #include <dbus/dbus.h>
55 #include <sys/socket.h>
56 #endif
57 
58 using namespace desktop;
59 using namespace ::com::sun::star::uno;
60 using namespace ::com::sun::star::lang;
61 using namespace ::com::sun::star::frame;
62 
63 namespace {
64 
65 char const ARGUMENT_PREFIX[] = "InternalIPC::Arguments";
66 char const SEND_ARGUMENTS[] = "InternalIPC::SendArguments";
67 char const PROCESSING_DONE[] = "InternalIPC::ProcessingDone";
68 
69 // Receives packets from the pipe until a packet ends in a NUL character (that
70 // will not be included in the returned string) or it cannot read anything (due
71 // to error or closed pipe, in which case an empty string will be returned to
72 // signal failure):
readStringFromPipe(osl::StreamPipe const & pipe)73 OString readStringFromPipe(osl::StreamPipe const & pipe) {
74     for (OStringBuffer str;;) {
75         char buf[1024];
76         sal_Int32 n = pipe.recv(buf, std::size(buf));
77         if (n <= 0) {
78             SAL_INFO("desktop.app", "read empty string");
79             return ""_ostr;
80         }
81         bool end = false;
82         if (buf[n - 1] == '\0') {
83             end = true;
84             --n;
85         }
86         str.append(buf, n);
87             //TODO: how does OStringBuffer.append handle overflow?
88         if (end) {
89             auto s = str.makeStringAndClear();
90             SAL_INFO("desktop.app", "read <" << s << ">");
91             return s;
92         }
93     }
94 }
95 
96 }
97 
98 namespace desktop
99 {
100 
101 namespace {
102 
103 class Parser: public CommandLineArgs::Supplier {
104 public:
Parser(OString input)105     explicit Parser(OString input): m_input(std::move(input)) {
106         if (!m_input.match(ARGUMENT_PREFIX) ||
107             m_input.getLength() == RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX))
108         {
109             throw CommandLineArgs::Supplier::Exception();
110         }
111         m_index = RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX);
112         switch (m_input[m_index++]) {
113         case '0':
114             break;
115         case '1':
116             {
117                 OUString url;
118                 if (!next(url, false)) {
119                     throw CommandLineArgs::Supplier::Exception();
120                 }
121                 m_cwdUrl = url;
122                 break;
123             }
124         case '2':
125             {
126                 OUString path;
127                 if (!next(path, false)) {
128                     throw CommandLineArgs::Supplier::Exception();
129                 }
130                 OUString url;
131                 if (osl::FileBase::getFileURLFromSystemPath(path, url) ==
132                     osl::FileBase::E_None)
133                 {
134                     m_cwdUrl = url;
135                 }
136                 break;
137             }
138         default:
139             throw CommandLineArgs::Supplier::Exception();
140         }
141     }
142 
getCwdUrl()143     virtual std::optional< OUString > getCwdUrl() override { return m_cwdUrl; }
144 
next(OUString & argument)145     virtual bool next(OUString& argument) override { return next(argument, true); }
146 
147 private:
next(OUString & argument,bool prefix)148     bool next(OUString& argument, bool prefix) {
149         if (m_index < m_input.getLength()) {
150             if (prefix) {
151                 if (m_input[m_index] != ',') {
152                     throw CommandLineArgs::Supplier::Exception();
153                 }
154                 ++m_index;
155             }
156             OStringBuffer b;
157             while (m_index < m_input.getLength()) {
158                 char c = m_input[m_index];
159                 if (c == ',') {
160                     break;
161                 }
162                 ++m_index;
163                 if (c == '\\') {
164                     if (m_index >= m_input.getLength())
165                         throw CommandLineArgs::Supplier::Exception();
166                     c = m_input[m_index++];
167                     switch (c) {
168                     case '0':
169                         c = '\0';
170                         break;
171                     case ',':
172                     case '\\':
173                         break;
174                     default:
175                         throw CommandLineArgs::Supplier::Exception();
176                     }
177                 }
178                 b.append(c);
179             }
180             OString b2(b.makeStringAndClear());
181             if (!rtl_convertStringToUString(
182                     &argument.pData, b2.getStr(), b2.getLength(),
183                     RTL_TEXTENCODING_UTF8,
184                     (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR |
185                      RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR |
186                      RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
187             {
188                 throw CommandLineArgs::Supplier::Exception();
189             }
190             return true;
191         } else {
192             return false;
193         }
194     }
195 
196     std::optional< OUString > m_cwdUrl;
197     OString m_input;
198     sal_Int32 m_index;
199 };
200 
addArgument(OStringBuffer & rArguments,char prefix,const OUString & rArgument)201 bool addArgument(OStringBuffer &rArguments, char prefix,
202     const OUString &rArgument)
203 {
204     OString utf8;
205     if (!rArgument.convertToString(
206             &utf8, RTL_TEXTENCODING_UTF8,
207             (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
208              RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
209     {
210         return false;
211     }
212     rArguments.append(prefix);
213     for (sal_Int32 i = 0; i < utf8.getLength(); ++i) {
214         char c = utf8[i];
215         switch (c) {
216         case '\0':
217             rArguments.append("\\0");
218             break;
219         case ',':
220             rArguments.append("\\,");
221             break;
222         case '\\':
223             rArguments.append("\\\\");
224             break;
225         default:
226             rArguments.append(c);
227             break;
228         }
229     }
230     return true;
231 }
232 
233 }
234 
235 rtl::Reference< RequestHandler > RequestHandler::pGlobal;
236 
237 // Turns a string in aMsg such as file:///home/foo/.libreoffice/3
238 // Into a hex string of well known length ff132a86...
CreateMD5FromString(const OUString & aMsg)239 static OUString CreateMD5FromString( const OUString& aMsg )
240 {
241     SAL_INFO("desktop.app", "create md5 from '" << aMsg << "'");
242 
243     rtlDigest handle = rtl_digest_create( rtl_Digest_AlgorithmMD5 );
244     if ( handle )
245     {
246         const sal_uInt8* pData = reinterpret_cast<const sal_uInt8*>(aMsg.getStr());
247         sal_uInt32       nSize = aMsg.getLength() * sizeof( sal_Unicode );
248         sal_uInt32       nMD5KeyLen = rtl_digest_queryLength( handle );
249         std::unique_ptr<sal_uInt8[]> pMD5KeyBuffer(new sal_uInt8[ nMD5KeyLen ]);
250 
251         rtl_digest_init( handle, pData, nSize );
252         rtl_digest_update( handle, pData, nSize );
253         rtl_digest_get( handle, pMD5KeyBuffer.get(), nMD5KeyLen );
254         rtl_digest_destroy( handle );
255 
256         // Create hex-value string from the MD5 value to keep the string size minimal
257         OUStringBuffer aBuffer( nMD5KeyLen * 2 + 1 );
258         for ( sal_uInt32 i = 0; i < nMD5KeyLen; i++ )
259             aBuffer.append( static_cast<sal_Int32>(pMD5KeyBuffer[i]), 16 );
260 
261         return aBuffer.makeStringAndClear();
262     }
263 
264     return OUString();
265 }
266 
267 namespace {
268 
269 class ProcessEventsClass_Impl
270 {
271 public:
272     DECL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void*, void );
273     DECL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void*, void );
274 };
275 
276 }
277 
IMPL_STATIC_LINK(ProcessEventsClass_Impl,CallEvent,void *,pEvent,void)278 IMPL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void*, pEvent, void )
279 {
280     // Application events are processed by the Desktop::HandleAppEvent implementation.
281     Desktop::HandleAppEvent( *static_cast<ApplicationEvent*>(pEvent) );
282     delete static_cast<ApplicationEvent*>(pEvent);
283 }
284 
IMPL_STATIC_LINK(ProcessEventsClass_Impl,ProcessDocumentsEvent,void *,pEvent,void)285 IMPL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void*, pEvent, void )
286 {
287     // Documents requests are processed by the RequestHandler implementation
288     ProcessDocumentsRequest* pDocsRequest = static_cast<ProcessDocumentsRequest*>(pEvent);
289     RequestHandler::ExecuteCmdLineRequests(*pDocsRequest, false);
290     delete pDocsRequest;
291 }
292 
ImplPostForeignAppEvent(ApplicationEvent * pEvent)293 static void ImplPostForeignAppEvent( ApplicationEvent* pEvent )
294 {
295     Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, CallEvent ), pEvent );
296 }
297 
ImplPostProcessDocumentsEvent(std::unique_ptr<ProcessDocumentsRequest> pEvent)298 static void ImplPostProcessDocumentsEvent( std::unique_ptr<ProcessDocumentsRequest> pEvent )
299 {
300     Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, ProcessDocumentsEvent ), pEvent.release() );
301 }
302 
SalMainPipeExchangeSignal_impl(SAL_UNUSED_PARAMETER void *,oslSignalInfo * pInfo)303 oslSignalAction SalMainPipeExchangeSignal_impl(SAL_UNUSED_PARAMETER void* /*pData*/, oslSignalInfo* pInfo)
304 {
305     if( pInfo->Signal == osl_Signal_Terminate )
306         RequestHandler::Disable();
307     return osl_Signal_ActCallNextHdl;
308 }
309 
310 
311 // The RequestHandlerController implementation is a bookkeeper for all pending requests
312 // that were created by the RequestHandler. The requests are waiting to be processed by
313 // our framework loadComponentFromURL function (e.g. open/print request).
314 // During shutdown the framework is asking RequestHandlerController about pending requests.
315 // If there are pending requests framework has to stop the shutdown process. It is waiting
316 // for these requests because framework is not able to handle shutdown and open a document
317 // concurrently.
318 
319 
320 // XServiceInfo
getImplementationName()321 OUString SAL_CALL RequestHandlerController::getImplementationName()
322 {
323     return u"com.sun.star.comp.RequestHandlerController"_ustr;
324 }
325 
supportsService(OUString const & ServiceName)326 sal_Bool RequestHandlerController::supportsService(
327     OUString const & ServiceName)
328 {
329     return cppu::supportsService(this, ServiceName);
330 }
331 
getSupportedServiceNames()332 Sequence< OUString > SAL_CALL RequestHandlerController::getSupportedServiceNames()
333 {
334     return { };
335 }
336 
337 // XEventListener
disposing(const EventObject &)338 void SAL_CALL RequestHandlerController::disposing( const EventObject& )
339 {
340 }
341 
342 // XTerminateListener
queryTermination(const EventObject &)343 void SAL_CALL RequestHandlerController::queryTermination( const EventObject& )
344 {
345     // Desktop ask about pending request through our office ipc pipe. We have to
346     // be sure that no pending request is waiting because framework is not able to
347     // handle shutdown and open a document concurrently.
348 
349     if ( RequestHandler::AreRequestsPending() )
350         throw TerminationVetoException();
351     RequestHandler::SetDowning();
352 }
353 
notifyTermination(const EventObject &)354 void SAL_CALL RequestHandlerController::notifyTermination( const EventObject& )
355 {
356 }
357 
358 class IpcThread: public salhelper::Thread {
359 public:
start(RequestHandler * handler)360     void start(RequestHandler * handler) {
361         m_handler = handler;
362         launch();
363     }
364 
365     virtual void close() = 0;
366 
367 protected:
IpcThread(char const * name)368     explicit IpcThread(char const * name): Thread(name), m_handler(nullptr) {}
369 
~IpcThread()370     virtual ~IpcThread() override {}
371 
372     bool process(OString const & arguments, bool * waitProcessed);
373 
374     RequestHandler * m_handler;
375 };
376 
377 class PipeIpcThread: public IpcThread {
378 public:
379     static RequestHandler::Status enable(rtl::Reference<IpcThread> * thread);
380 
381 private:
PipeIpcThread(osl::Pipe pipe)382     explicit PipeIpcThread(osl::Pipe pipe):
383         IpcThread("PipeIPC"), pipe_(std::move(pipe))
384     {}
385 
~PipeIpcThread()386     virtual ~PipeIpcThread() override {}
387 
388     void execute() override;
389 
close()390     void close() override { pipe_.close(); }
391 
392     osl::Pipe pipe_;
393 };
394 
395 #if ENABLE_DBUS
396 
397 namespace {
398 
399 struct DbusConnectionHolder {
DbusConnectionHolderdesktop::__anonbb17026f0411::DbusConnectionHolder400     explicit DbusConnectionHolder(DBusConnection * theConnection):
401         connection(theConnection)
402     {}
403 
DbusConnectionHolderdesktop::__anonbb17026f0411::DbusConnectionHolder404     DbusConnectionHolder(DbusConnectionHolder && other): connection(nullptr)
405     { std::swap(connection, other.connection); }
406 
~DbusConnectionHolderdesktop::__anonbb17026f0411::DbusConnectionHolder407     ~DbusConnectionHolder() {
408         if (connection != nullptr) {
409             dbus_connection_close(connection);
410             dbus_connection_unref(connection);
411         }
412     }
413 
414     DBusConnection * connection;
415 };
416 
417 struct DbusMessageHolder {
DbusMessageHolderdesktop::__anonbb17026f0411::DbusMessageHolder418     explicit DbusMessageHolder(DBusMessage * theMessage): message(theMessage) {}
419 
~DbusMessageHolderdesktop::__anonbb17026f0411::DbusMessageHolder420     ~DbusMessageHolder() { clear(); }
421 
cleardesktop::__anonbb17026f0411::DbusMessageHolder422     void clear() {
423         if (message != nullptr) {
424             dbus_message_unref(message);
425         }
426         message = nullptr;
427     }
428 
429     DBusMessage * message;
430 
431 private:
432     DbusMessageHolder(DbusMessageHolder const &) = delete;
433     DbusMessageHolder& operator =(DbusMessageHolder const &) = delete;
434 };
435 
436 }
437 
438 class DbusIpcThread: public IpcThread {
439 public:
440     static RequestHandler::Status enable(rtl::Reference<IpcThread> * thread);
441 
442 private:
DbusIpcThread(DbusConnectionHolder && connection)443     explicit DbusIpcThread(DbusConnectionHolder && connection):
444         IpcThread("DbusIPC"), connection_(std::move(connection))
445     {}
446 
~DbusIpcThread()447     virtual ~DbusIpcThread() override {}
448 
449     void execute() override;
450 
451     void close() override;
452 
453     DbusConnectionHolder connection_;
454 };
455 
enable(rtl::Reference<IpcThread> * thread)456 RequestHandler::Status DbusIpcThread::enable(rtl::Reference<IpcThread> * thread)
457 {
458     assert(thread != nullptr);
459     if (!dbus_threads_init_default()) {
460         SAL_WARN("desktop.app", "dbus_threads_init_default failed");
461         return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
462     }
463     DBusError e;
464     dbus_error_init(&e);
465     DbusConnectionHolder con(dbus_bus_get_private(DBUS_BUS_SESSION, &e));
466     assert((con.connection == nullptr) == bool(dbus_error_is_set(&e)));
467     if (con.connection == nullptr) {
468         SAL_WARN(
469             "desktop.app",
470             "dbus_bus_get_private failed with: " << e.name << ": "
471                 << e.message);
472         dbus_error_free(&e);
473         return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
474     }
475     for (;;) {
476         int n = dbus_bus_request_name(
477             con.connection, "org.libreoffice.LibreOfficeIpc0",
478             DBUS_NAME_FLAG_DO_NOT_QUEUE, &e);
479         assert((n == -1) == bool(dbus_error_is_set(&e)));
480         switch (n) {
481         case -1:
482             SAL_WARN(
483                 "desktop.app",
484                 "dbus_bus_request_name failed with: " << e.name << ": "
485                 << e.message);
486             dbus_error_free(&e);
487             return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
488         case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
489             *thread = new DbusIpcThread(std::move(con));
490             return RequestHandler::IPC_STATUS_OK;
491         case DBUS_REQUEST_NAME_REPLY_EXISTS:
492             {
493                 OStringBuffer buf(ARGUMENT_PREFIX);
494                 OUString arg;
495                 if (!(utl::Bootstrap::getProcessWorkingDir(arg)
496                       && addArgument(buf, '1', arg)))
497                 {
498                     buf.append('0');
499                 }
500                 sal_uInt32 narg = rtl_getAppCommandArgCount();
501                 for (sal_uInt32 i = 0; i != narg; ++i) {
502                     rtl_getAppCommandArg(i, &arg.pData);
503                     if (!addArgument(buf, ',', arg)) {
504                         return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
505                     }
506                 }
507                 char const * argstr = buf.getStr();
508                 DbusMessageHolder msg(
509                     dbus_message_new_method_call(
510                         "org.libreoffice.LibreOfficeIpc0",
511                         "/org/libreoffice/LibreOfficeIpc0",
512                         "org.libreoffice.LibreOfficeIpcIfc0", "Execute"));
513                 if (msg.message == nullptr) {
514                     SAL_WARN(
515                         "desktop.app", "dbus_message_new_method_call failed");
516                     return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
517                 }
518                 DBusMessageIter it;
519                 dbus_message_iter_init_append(msg.message, &it);
520                 if (!dbus_message_iter_append_basic(
521                         &it, DBUS_TYPE_STRING, &argstr))
522                 {
523                     SAL_WARN(
524                         "desktop.app", "dbus_message_iter_append_basic failed");
525                     return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
526                 }
527                 DbusMessageHolder repl(
528                     dbus_connection_send_with_reply_and_block(
529                         con.connection, msg.message, 0x7FFFFFFF, &e));
530                 assert(
531                     (repl.message == nullptr) == bool(dbus_error_is_set(&e)));
532                 if (repl.message == nullptr) {
533                     SAL_INFO(
534                         "desktop.app",
535                         "dbus_connection_send_with_reply_and_block failed"
536                             " with: " << e.name << ": " << e.message);
537                     dbus_error_free(&e);
538                     break;
539                 }
540                 return RequestHandler::IPC_STATUS_2ND_OFFICE;
541             }
542         case DBUS_REQUEST_NAME_REPLY_IN_QUEUE:
543         case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
544             SAL_WARN(
545                 "desktop.app",
546                 "dbus_bus_request_name failed with unexpected " << +n);
547             return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
548         default:
549             for (;;) std::abort();
550         }
551     }
552 }
553 
execute()554 void DbusIpcThread::execute()
555 {
556     assert(m_handler != nullptr);
557     m_handler->cReady.wait();
558     for (;;) {
559         {
560             osl::MutexGuard g(RequestHandler::GetMutex());
561             if (m_handler->mState == RequestHandler::State::Downing) {
562                 break;
563             }
564         }
565         if (!dbus_connection_read_write(connection_.connection, -1)) {
566             break;
567         }
568         for (;;) {
569             DbusMessageHolder msg(
570                 dbus_connection_pop_message(connection_.connection));
571             if (msg.message == nullptr) {
572                 break;
573             }
574             if (!dbus_message_is_method_call(
575                     msg.message, "org.libreoffice.LibreOfficeIpcIfc0",
576                     "Execute"))
577             {
578                 SAL_INFO("desktop.app", "unknown DBus message ignored");
579                 continue;
580             }
581             DBusMessageIter it;
582             if (!dbus_message_iter_init(msg.message, &it)) {
583                 SAL_WARN(
584                     "desktop.app", "DBus message without argument ignored");
585                 continue;
586             }
587             if (dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_STRING) {
588                 SAL_WARN(
589                     "desktop.app",
590                     "DBus message with non-string argument ignored");
591                 continue;
592             }
593             char const * argstr;
594             dbus_message_iter_get_basic(&it, &argstr);
595             bool waitProcessed = false;
596             {
597                 osl::MutexGuard g(RequestHandler::GetMutex());
598                 if (!process(argstr, &waitProcessed)) {
599                     continue;
600                 }
601             }
602             if (waitProcessed) {
603                 m_handler->cProcessed.wait();
604             }
605             DbusMessageHolder repl(dbus_message_new_method_return(msg.message));
606             if (repl.message == nullptr) {
607                 SAL_WARN(
608                     "desktop.app", "dbus_message_new_method_return failed");
609                 continue;
610             }
611             dbus_uint32_t serial = 0;
612             if (!dbus_connection_send(
613                     connection_.connection, repl.message, &serial)) {
614                 SAL_WARN("desktop.app", "dbus_connection_send failed");
615                 continue;
616             }
617             dbus_connection_flush(connection_.connection);
618         }
619     }
620 }
621 
close()622 void DbusIpcThread::close() {
623     assert(connection_.connection != nullptr);
624     // Make dbus_connection_read_write fall out of internal poll call blocking
625     // on POLLIN:
626     int fd;
627     if (!dbus_connection_get_socket(connection_.connection, &fd)) {
628         SAL_WARN("desktop.app", "dbus_connection_get_socket failed");
629         return;
630     }
631     if (shutdown(fd, SHUT_RD) == -1) {
632         auto const e = errno;
633         SAL_WARN("desktop.app", "shutdown failed with errno " << e);
634     }
635 }
636 
637 #endif
638 
GetMutex()639 ::osl::Mutex& RequestHandler::GetMutex()
640 {
641     static ::osl::Mutex theRequestHandlerMutex;
642     return theRequestHandlerMutex;
643 }
644 
SetDowning()645 void RequestHandler::SetDowning()
646 {
647     // We have the order to block all incoming requests. Framework
648     // wants to shutdown and we have to make sure that no loading/printing
649     // requests are executed anymore.
650     ::osl::MutexGuard   aGuard( GetMutex() );
651 
652     if ( pGlobal.is() )
653         pGlobal->mState = State::Downing;
654 }
655 
EnableRequests()656 void RequestHandler::EnableRequests()
657 {
658     // switch between just queueing the requests and executing them
659     ::osl::MutexGuard   aGuard( GetMutex() );
660 
661     if ( pGlobal.is() )
662     {
663         if (pGlobal->mState != State::Downing) {
664             pGlobal->mState = State::RequestsEnabled;
665         }
666         ProcessDocumentsRequest aEmptyReq(std::nullopt);
667         // trigger already queued requests
668         RequestHandler::ExecuteCmdLineRequests(aEmptyReq, true);
669     }
670 }
671 
AreRequestsPending()672 bool RequestHandler::AreRequestsPending()
673 {
674     // Give info about pending requests
675     ::osl::MutexGuard   aGuard( GetMutex() );
676     if ( pGlobal.is() )
677         return ( pGlobal->mnPendingRequests > 0 );
678     else
679         return false;
680 }
681 
RequestsCompleted()682 void RequestHandler::RequestsCompleted()
683 {
684     // Remove nCount pending requests from our internal counter
685     ::osl::MutexGuard   aGuard( GetMutex() );
686     if ( pGlobal.is() )
687     {
688         if ( pGlobal->mnPendingRequests > 0 )
689             pGlobal->mnPendingRequests --;
690     }
691 }
692 
Enable(bool ipc)693 RequestHandler::Status RequestHandler::Enable(bool ipc)
694 {
695     ::osl::MutexGuard   aGuard( GetMutex() );
696 
697     if( pGlobal.is() )
698         return IPC_STATUS_OK;
699 
700 #if !HAVE_FEATURE_DESKTOP || HAVE_FEATURE_MACOSX_SANDBOX || defined(EMSCRIPTEN)
701     ipc = false;
702 #endif
703 
704     if (!ipc) {
705         pGlobal = new RequestHandler;
706         return IPC_STATUS_OK;
707     }
708 
709     enum class Kind { Pipe, Dbus };
710     Kind kind;
711 #if ENABLE_DBUS
712     kind = std::getenv("LIBO_FLATPAK") != nullptr ? Kind::Dbus : Kind::Pipe;
713 #else
714     kind = Kind::Pipe;
715 #endif
716     rtl::Reference<IpcThread> thread;
717     Status stat = Status(); // silence bogus potentially-uninitialized warnings
718     switch (kind) {
719     case Kind::Pipe:
720         stat = PipeIpcThread::enable(&thread);
721         break;
722     case Kind::Dbus:
723 #if ENABLE_DBUS
724         stat = DbusIpcThread::enable(&thread);
725         break;
726 #endif
727     default:
728         assert(false);
729     }
730     assert(thread.is() == (stat == IPC_STATUS_OK));
731     if (stat == IPC_STATUS_OK) {
732         pGlobal = new RequestHandler;
733         pGlobal->mIpcThread = thread;
734         pGlobal->mIpcThread->start(pGlobal.get());
735     }
736     return stat;
737 }
738 
enable(rtl::Reference<IpcThread> * thread)739 RequestHandler::Status PipeIpcThread::enable(rtl::Reference<IpcThread> * thread)
740 {
741     assert(thread != nullptr);
742 
743     // The name of the named pipe is created with the hashcode of the user installation directory (without /user). We have to retrieve
744     // this information from a unotools implementation.
745     OUString aUserInstallPath;
746     ::utl::Bootstrap::PathStatus aLocateResult = ::utl::Bootstrap::locateUserInstallation( aUserInstallPath );
747     if (aLocateResult != utl::Bootstrap::PATH_EXISTS
748         && aLocateResult != utl::Bootstrap::PATH_VALID)
749     {
750         return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
751     }
752 
753     // Try to  determine if we are the first office or not! This should prevent multiple
754     // access to the user directory !
755     // First we try to create our pipe if this fails we try to connect. We have to do this
756     // in a loop because the other office can crash or shutdown between createPipe
757     // and connectPipe!!
758     auto aUserInstallPathHashCode = CreateMD5FromString(aUserInstallPath);
759 
760     // Check result to create a hash code from the user install path
761     if ( aUserInstallPathHashCode.isEmpty() )
762         return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR; // Something completely broken, we cannot create a valid hash code!
763 
764     osl::Pipe pipe;
765     enum PipeMode
766     {
767         PIPEMODE_DONTKNOW,
768         PIPEMODE_CREATED,
769         PIPEMODE_CONNECTED
770     };
771     PipeMode nPipeMode = PIPEMODE_DONTKNOW;
772 
773     OUString aPipeIdent( "SingleOfficeIPC_" + aUserInstallPathHashCode );
774     do
775     {
776         osl::Security security;
777 
778         // Try to create pipe
779         if ( pipe.create( aPipeIdent, osl_Pipe_CREATE, security ))
780         {
781             // Pipe created
782             nPipeMode = PIPEMODE_CREATED;
783         }
784         else if( pipe.create( aPipeIdent, osl_Pipe_OPEN, security )) // Creation not successful, now we try to connect
785         {
786             osl::StreamPipe aStreamPipe(pipe.getHandle());
787             if (readStringFromPipe(aStreamPipe) == SEND_ARGUMENTS)
788             {
789                 // Pipe connected to first office
790                 nPipeMode = PIPEMODE_CONNECTED;
791             }
792             else
793             {
794                 // Pipe connection failed (other office exited or crashed)
795                 std::this_thread::sleep_for( std::chrono::milliseconds(500) );
796             }
797         }
798         else
799         {
800             oslPipeError eReason = pipe.getError();
801             if ((eReason == osl_Pipe_E_ConnectionRefused) || (eReason == osl_Pipe_E_invalidError))
802                 return RequestHandler::IPC_STATUS_PIPE_ERROR;
803 
804             // Wait for second office to be ready
805             std::this_thread::sleep_for( std::chrono::milliseconds(10) );
806         }
807 
808     } while ( nPipeMode == PIPEMODE_DONTKNOW );
809 
810     if ( nPipeMode == PIPEMODE_CREATED )
811     {
812         // Seems we are the one and only, so create listening thread
813         *thread = new PipeIpcThread(pipe);
814         return RequestHandler::IPC_STATUS_OK;
815     }
816     else
817     {
818         // Seems another office is running. Pipe arguments to it and self terminate
819         osl::StreamPipe aStreamPipe(pipe.getHandle());
820 
821         OStringBuffer aArguments(ARGUMENT_PREFIX);
822         OUString cwdUrl;
823         if (!(utl::Bootstrap::getProcessWorkingDir(cwdUrl) &&
824               addArgument(aArguments, '1', cwdUrl)))
825         {
826             aArguments.append('0');
827         }
828         sal_uInt32 nCount = rtl_getAppCommandArgCount();
829         for( sal_uInt32 i=0; i < nCount; i++ )
830         {
831             rtl_getAppCommandArg( i, &aUserInstallPath.pData );
832             if (!addArgument(aArguments, ',', aUserInstallPath)) {
833                 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
834             }
835         }
836         aArguments.append('\0');
837         // finally, write the string onto the pipe
838         SAL_INFO("desktop.app", "writing <" << aArguments.getStr() << ">");
839         sal_Int32 n = aStreamPipe.write(
840             aArguments.getStr(), aArguments.getLength());
841         if (n != aArguments.getLength()) {
842             SAL_INFO("desktop.app", "short write: " << n);
843             return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
844         }
845 
846         if (readStringFromPipe(aStreamPipe) != PROCESSING_DONE)
847         {
848             // something went wrong
849             return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
850         }
851 
852         return RequestHandler::IPC_STATUS_2ND_OFFICE;
853     }
854 }
855 
Disable()856 void RequestHandler::Disable()
857 {
858     osl::ClearableMutexGuard aMutex( GetMutex() );
859 
860     if( !pGlobal.is() )
861         return;
862 
863     rtl::Reference< RequestHandler > handler(pGlobal);
864     pGlobal.clear();
865 
866     handler->mState = State::Downing;
867     if (handler->mIpcThread.is()) {
868         handler->mIpcThread->close();
869     }
870 
871     // release mutex to avoid deadlocks
872     aMutex.clear();
873 
874     handler->cReady.set();
875 
876     // exit gracefully and join
877     if (handler->mIpcThread.is())
878     {
879         handler->mIpcThread->join();
880         handler->mIpcThread.clear();
881     }
882 
883     handler->cReady.reset();
884 }
885 
RequestHandler()886 RequestHandler::RequestHandler() :
887     mState( State::Starting ),
888     mnPendingRequests( 0 )
889 {
890 }
891 
~RequestHandler()892 RequestHandler::~RequestHandler()
893 {
894     assert(!mIpcThread.is());
895 }
896 
SetReady(bool bIsReady)897 void RequestHandler::SetReady(bool bIsReady)
898 {
899     osl::MutexGuard g(GetMutex());
900     if (pGlobal.is())
901     {
902         if (bIsReady)
903             pGlobal->cReady.set();
904         else
905             pGlobal->cReady.reset();
906     }
907 }
908 
WaitForReady()909 void RequestHandler::WaitForReady()
910 {
911     rtl::Reference<RequestHandler> t;
912     {
913         osl::MutexGuard g(GetMutex());
914         t =  pGlobal;
915     }
916     if (t.is())
917     {
918         t->cReady.wait();
919     }
920 }
921 
process(OString const & arguments,bool * waitProcessed)922 bool IpcThread::process(OString const & arguments, bool * waitProcessed) {
923     assert(waitProcessed != nullptr);
924 
925     std::unique_ptr< CommandLineArgs > aCmdLineArgs;
926     try
927     {
928         Parser p(arguments);
929         aCmdLineArgs.reset( new CommandLineArgs( p ) );
930     }
931     catch ( const CommandLineArgs::Supplier::Exception & )
932     {
933         SAL_WARN("desktop.app", "Error in received command line arguments");
934         return false;
935     }
936 
937     bool bDocRequestSent = false;
938 
939     OUString aUnknown( aCmdLineArgs->GetUnknown() );
940     if (aUnknown.isEmpty() && !aCmdLineArgs->IsHelp() && !aCmdLineArgs->IsVersion())
941     {
942         const CommandLineArgs &rCurrentCmdLineArgs = Desktop::GetCommandLineArgs();
943 
944         if ( aCmdLineArgs->IsQuickstart() )
945         {
946             // we have to use application event, because we have to start quickstart service in main thread!!
947             ApplicationEvent* pAppEvent =
948                 new ApplicationEvent(ApplicationEvent::Type::QuickStart);
949             ImplPostForeignAppEvent( pAppEvent );
950         }
951 
952         // handle request for acceptor
953         std::vector< OUString > const & accept = aCmdLineArgs->GetAccept();
954         for (auto const& elem : accept)
955         {
956             ApplicationEvent* pAppEvent = new ApplicationEvent(
957                 ApplicationEvent::Type::Accept, elem);
958             ImplPostForeignAppEvent( pAppEvent );
959         }
960         // handle acceptor removal
961         std::vector< OUString > const & unaccept = aCmdLineArgs->GetUnaccept();
962         for (auto const& elem : unaccept)
963         {
964             ApplicationEvent* pAppEvent = new ApplicationEvent(
965                 ApplicationEvent::Type::Unaccept, elem);
966             ImplPostForeignAppEvent( pAppEvent );
967         }
968 
969         std::unique_ptr<ProcessDocumentsRequest> pRequest(new ProcessDocumentsRequest(
970             aCmdLineArgs->getCwdUrl()));
971         m_handler->cProcessed.reset();
972         pRequest->pcProcessed = &m_handler->cProcessed;
973         m_handler->mbSuccess = false;
974         pRequest->mpbSuccess = &m_handler->mbSuccess;
975 
976         // Print requests are not dependent on the --invisible cmdline argument as they are
977         // loaded with the "hidden" flag! So they are always checked.
978         pRequest->aPrintList = aCmdLineArgs->GetPrintList();
979         bDocRequestSent |= !pRequest->aPrintList.empty();
980         pRequest->aPrintToList = aCmdLineArgs->GetPrintToList();
981         pRequest->aPrinterName = aCmdLineArgs->GetPrinterName();
982         bDocRequestSent |= !( pRequest->aPrintToList.empty() || pRequest->aPrinterName.isEmpty() );
983         pRequest->aConversionList = aCmdLineArgs->GetConversionList();
984         pRequest->aConversionParams = aCmdLineArgs->GetConversionParams();
985         pRequest->aConversionOut = aCmdLineArgs->GetConversionOut();
986         pRequest->aImageConversionType = aCmdLineArgs->GetImageConversionType();
987         pRequest->aStartListParams = aCmdLineArgs->GetStartListParams();
988         pRequest->aInFilter = aCmdLineArgs->GetInFilter();
989         pRequest->bTextCat = aCmdLineArgs->IsTextCat();
990         pRequest->bScriptCat = aCmdLineArgs->IsScriptCat();
991         bDocRequestSent |= !pRequest->aConversionList.empty();
992 
993         if ( !rCurrentCmdLineArgs.IsInvisible() )
994         {
995             // Read cmdline args that can open/create documents. As they would open a window
996             // they are only allowed if the "--invisible" is currently not used!
997             pRequest->aOpenList = aCmdLineArgs->GetOpenList();
998             bDocRequestSent |= !pRequest->aOpenList.empty();
999             pRequest->aViewList = aCmdLineArgs->GetViewList();
1000             bDocRequestSent |= !pRequest->aViewList.empty();
1001             pRequest->aStartList = aCmdLineArgs->GetStartList();
1002             bDocRequestSent |= !pRequest->aStartList.empty();
1003             pRequest->aForceOpenList = aCmdLineArgs->GetForceOpenList();
1004             bDocRequestSent |= !pRequest->aForceOpenList.empty();
1005             pRequest->aForceNewList = aCmdLineArgs->GetForceNewList();
1006             bDocRequestSent |= !pRequest->aForceNewList.empty();
1007 
1008             // Special command line args to create an empty document for a given module
1009 
1010             // #i18338# (lo)
1011             // we only do this if no document was specified on the command line,
1012             // since this would be inconsistent with the behaviour of
1013             // the first process, see OpenClients() (call to OpenDefault()) in app.cxx
1014             if ( aCmdLineArgs->HasModuleParam() && !bDocRequestSent )
1015             {
1016                 SvtModuleOptions aOpt;
1017                 SvtModuleOptions::EFactory eFactory = SvtModuleOptions::EFactory::WRITER;
1018                 if ( aCmdLineArgs->IsWriter() )
1019                     eFactory = SvtModuleOptions::EFactory::WRITER;
1020                 else if ( aCmdLineArgs->IsCalc() )
1021                     eFactory = SvtModuleOptions::EFactory::CALC;
1022                 else if ( aCmdLineArgs->IsDraw() )
1023                     eFactory = SvtModuleOptions::EFactory::DRAW;
1024                 else if ( aCmdLineArgs->IsImpress() )
1025                     eFactory = SvtModuleOptions::EFactory::IMPRESS;
1026                 else if ( aCmdLineArgs->IsBase() )
1027                     eFactory = SvtModuleOptions::EFactory::DATABASE;
1028                 else if ( aCmdLineArgs->IsMath() )
1029                     eFactory = SvtModuleOptions::EFactory::MATH;
1030                 else if ( aCmdLineArgs->IsGlobal() )
1031                     eFactory = SvtModuleOptions::EFactory::WRITERGLOBAL;
1032                 else if ( aCmdLineArgs->IsWeb() )
1033                     eFactory = SvtModuleOptions::EFactory::WRITERWEB;
1034 
1035                 if ( !pRequest->aOpenList.empty() )
1036                     pRequest->aModule = aOpt.GetFactoryName( eFactory );
1037                 else
1038                     pRequest->aOpenList.push_back( aOpt.GetFactoryEmptyDocumentURL( eFactory ) );
1039                 bDocRequestSent = true;
1040             }
1041         }
1042 
1043         if ( !aCmdLineArgs->IsQuickstart() ) {
1044             bool bShowHelp = false;
1045             OUStringBuffer aHelpURLBuffer;
1046             if (aCmdLineArgs->IsHelpWriter()) {
1047                 bShowHelp = true;
1048                 aHelpURLBuffer.append("vnd.sun.star.help://swriter/start");
1049             } else if (aCmdLineArgs->IsHelpCalc()) {
1050                 bShowHelp = true;
1051                 aHelpURLBuffer.append("vnd.sun.star.help://scalc/start");
1052             } else if (aCmdLineArgs->IsHelpDraw()) {
1053                 bShowHelp = true;
1054                 aHelpURLBuffer.append("vnd.sun.star.help://sdraw/start");
1055             } else if (aCmdLineArgs->IsHelpImpress()) {
1056                 bShowHelp = true;
1057                 aHelpURLBuffer.append("vnd.sun.star.help://simpress/start");
1058             } else if (aCmdLineArgs->IsHelpBase()) {
1059                 bShowHelp = true;
1060                 aHelpURLBuffer.append("vnd.sun.star.help://sdatabase/start");
1061             } else if (aCmdLineArgs->IsHelpBasic()) {
1062                 bShowHelp = true;
1063                 aHelpURLBuffer.append("vnd.sun.star.help://sbasic/start");
1064             } else if (aCmdLineArgs->IsHelpMath()) {
1065                 bShowHelp = true;
1066                 aHelpURLBuffer.append("vnd.sun.star.help://smath/start");
1067             }
1068             if (bShowHelp) {
1069                 aHelpURLBuffer.append("?Language="
1070                     + utl::ConfigManager::getUILocale()
1071 #if defined UNX
1072                     + "&System=UNX");
1073 #elif defined _WIN32
1074                     + "&System=WIN");
1075 #endif
1076                 ApplicationEvent* pAppEvent = new ApplicationEvent(
1077                     ApplicationEvent::Type::OpenHelpUrl,
1078                     aHelpURLBuffer.makeStringAndClear());
1079                 ImplPostForeignAppEvent( pAppEvent );
1080             }
1081         }
1082 
1083         if ( bDocRequestSent )
1084         {
1085             // Send requests to dispatch watcher if we have at least one. The receiver
1086             // is responsible to delete the request after processing it.
1087             if ( aCmdLineArgs->HasModuleParam() )
1088             {
1089                 SvtModuleOptions    aOpt;
1090 
1091                 // Support command line parameters to start a module (as preselection)
1092                 if ( aCmdLineArgs->IsWriter() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
1093                     pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::WRITER );
1094                 else if ( aCmdLineArgs->IsCalc() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
1095                     pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::CALC );
1096                 else if ( aCmdLineArgs->IsImpress() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
1097                     pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::IMPRESS );
1098                 else if ( aCmdLineArgs->IsDraw() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
1099                     pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::DRAW );
1100             }
1101 
1102             ImplPostProcessDocumentsEvent( std::move(pRequest) );
1103         }
1104         else
1105         {
1106             // delete not used request again
1107             pRequest.reset();
1108         }
1109         if (aCmdLineArgs->IsEmpty())
1110         {
1111             // no document was sent, just bring Office to front
1112             ApplicationEvent* pAppEvent =
1113                 new ApplicationEvent(ApplicationEvent::Type::Appear);
1114             ImplPostForeignAppEvent( pAppEvent );
1115         }
1116     }
1117     *waitProcessed = bDocRequestSent;
1118     return true;
1119 }
1120 
execute()1121 void PipeIpcThread::execute()
1122 {
1123     assert(m_handler != nullptr);
1124     do
1125     {
1126         osl::StreamPipe aStreamPipe;
1127         oslPipeError nError = pipe_.accept( aStreamPipe );
1128 
1129 
1130         if( nError == osl_Pipe_E_None )
1131         {
1132             // if we receive a request while the office is displaying some dialog or error during
1133             // bootstrap, that dialogs event loop might get events that are dispatched by this thread
1134             // we have to wait for cReady to be set by the real main loop.
1135             // only requests that don't dispatch events may be processed before cReady is set.
1136             m_handler->cReady.wait();
1137 
1138             // we might have decided to shutdown while we were sleeping
1139             if (!RequestHandler::pGlobal.is()) return;
1140 
1141             // only lock the mutex when processing starts, otherwise we deadlock when the office goes
1142             // down during wait
1143             osl::ClearableMutexGuard aGuard( RequestHandler::GetMutex() );
1144 
1145             if (m_handler->mState == RequestHandler::State::Downing)
1146             {
1147                 break;
1148             }
1149 
1150             // notify client we're ready to process its args:
1151             SAL_INFO("desktop.app", "writing <" << SEND_ARGUMENTS << ">");
1152             std::size_t n = aStreamPipe.write(
1153                 SEND_ARGUMENTS, std::size(SEND_ARGUMENTS));
1154                 // incl. terminating NUL
1155             if (n != std::size(SEND_ARGUMENTS)) {
1156                 SAL_WARN("desktop.app", "short write: " << n);
1157                 continue;
1158             }
1159 
1160             OString aArguments = readStringFromPipe(aStreamPipe);
1161 
1162             // Is this a lookup message from another application? if so, ignore
1163             if (aArguments.isEmpty())
1164                 continue;
1165 
1166             bool waitProcessed = false;
1167             if (!process(aArguments, &waitProcessed)) {
1168                 continue;
1169             }
1170 
1171             // we don't need the mutex any longer...
1172             aGuard.clear();
1173             bool bSuccess = true;
1174             // wait for processing to finish
1175             if (waitProcessed)
1176             {
1177                 m_handler->cProcessed.wait();
1178                 bSuccess = m_handler->mbSuccess;
1179             }
1180             if (bSuccess)
1181             {
1182                 // processing finished, inform the requesting end:
1183                 SAL_INFO("desktop.app", "writing <" << PROCESSING_DONE << ">");
1184                 n = aStreamPipe.write(PROCESSING_DONE, std::size(PROCESSING_DONE));
1185                 // incl. terminating NUL
1186                 if (n != std::size(PROCESSING_DONE))
1187                 {
1188                     SAL_WARN("desktop.app", "short write: " << n);
1189                     continue;
1190                 }
1191             }
1192         }
1193         else
1194         {
1195             {
1196                 osl::MutexGuard aGuard( RequestHandler::GetMutex() );
1197                 if (m_handler->mState == RequestHandler::State::Downing)
1198                 {
1199                     break;
1200                 }
1201             }
1202 
1203             SAL_WARN( "desktop.app", "Error on accept: " << static_cast<int>(nError));
1204             std::this_thread::sleep_for( std::chrono::seconds(1) );
1205         }
1206     } while( schedule() );
1207 }
1208 
AddToDispatchList(std::vector<DispatchWatcher::DispatchRequest> & rDispatchList,std::optional<OUString> const & cwdUrl,std::vector<OUString> const & aRequestList,DispatchWatcher::RequestType nType,const OUString & aParam,const OUString & aFactory)1209 static void AddToDispatchList(
1210     std::vector<DispatchWatcher::DispatchRequest>& rDispatchList,
1211     std::optional< OUString > const & cwdUrl,
1212     std::vector< OUString > const & aRequestList,
1213     DispatchWatcher::RequestType nType,
1214     const OUString& aParam,
1215     const OUString& aFactory )
1216 {
1217     for (auto const& request : aRequestList)
1218     {
1219         rDispatchList.push_back({nType, request, cwdUrl, aParam, aFactory});
1220     }
1221 }
1222 
AddConversionsToDispatchList(std::vector<DispatchWatcher::DispatchRequest> & rDispatchList,std::optional<OUString> const & cwdUrl,std::vector<OUString> const & rRequestList,const OUString & rParam,const OUString & rPrinterName,const OUString & rFactory,const OUString & rParamOut,std::u16string_view rImgOut,const bool isTextCat,const bool isScriptCat)1223 static void AddConversionsToDispatchList(
1224     std::vector<DispatchWatcher::DispatchRequest>& rDispatchList,
1225     std::optional< OUString > const & cwdUrl,
1226     std::vector< OUString > const & rRequestList,
1227     const OUString& rParam,
1228     const OUString& rPrinterName,
1229     const OUString& rFactory,
1230     const OUString& rParamOut,
1231     std::u16string_view rImgOut,
1232     const bool isTextCat,
1233     const bool isScriptCat )
1234 {
1235     DispatchWatcher::RequestType nType;
1236     OUString aParam( rParam );
1237 
1238     if( !rParam.isEmpty() )
1239     {
1240         if ( isTextCat )
1241             nType = DispatchWatcher::REQUEST_CAT;
1242         else
1243             nType = DispatchWatcher::REQUEST_CONVERSION;
1244     }
1245     else
1246     {
1247         if ( isScriptCat )
1248             nType = DispatchWatcher::REQUEST_SCRIPT_CAT;
1249         else
1250         {
1251             nType = DispatchWatcher::REQUEST_BATCHPRINT;
1252             aParam = rPrinterName;
1253         }
1254     }
1255 
1256     OUString aPWD;
1257     if (cwdUrl)
1258     {
1259         aPWD = *cwdUrl;
1260     }
1261     else
1262     {
1263         utl::Bootstrap::getProcessWorkingDir( aPWD );
1264     }
1265 
1266     if (OUString aOutDir(rParamOut.trim()); !aOutDir.isEmpty())
1267     {
1268         if (osl::FileBase::getAbsoluteFileURL(aPWD, rParamOut, aOutDir) == osl::FileBase::E_None)
1269             osl::FileBase::getSystemPathFromFileURL(aOutDir, aOutDir);
1270         aParam += ";" + aOutDir;
1271     }
1272     else
1273     {
1274         ::osl::FileBase::getSystemPathFromFileURL( aPWD, aPWD );
1275         aParam += ";" + aPWD;
1276     }
1277 
1278     if( !rImgOut.empty() )
1279         aParam += OUString::Concat("|") + o3tl::trim(rImgOut);
1280 
1281     for (auto const& request : rRequestList)
1282     {
1283         rDispatchList.push_back({nType, request, cwdUrl, aParam, rFactory});
1284     }
1285 }
1286 
1287 namespace {
1288 
1289 struct ConditionSetGuard
1290 {
1291     osl::Condition* m_pCondition;
ConditionSetGuarddesktop::__anonbb17026f0511::ConditionSetGuard1292     ConditionSetGuard(osl::Condition* pCondition) : m_pCondition(pCondition) {}
~ConditionSetGuarddesktop::__anonbb17026f0511::ConditionSetGuard1293     ~ConditionSetGuard() { if (m_pCondition) m_pCondition->set(); }
1294 };
1295 
1296 }
1297 
ExecuteCmdLineRequests(ProcessDocumentsRequest & aRequest,bool noTerminate)1298 bool RequestHandler::ExecuteCmdLineRequests(
1299     ProcessDocumentsRequest& aRequest, bool noTerminate)
1300 {
1301     // protect the dispatch list
1302     osl::ClearableMutexGuard aGuard( GetMutex() );
1303 
1304     // ensure that Processed flag (if exists) is signaled in any outcome
1305     ConditionSetGuard aSetGuard(aRequest.pcProcessed);
1306 
1307     static std::vector<DispatchWatcher::DispatchRequest> aDispatchList;
1308 
1309     // Create dispatch list for dispatch watcher
1310     AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aInFilter, DispatchWatcher::REQUEST_INFILTER, u""_ustr, aRequest.aModule );
1311     AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aOpenList, DispatchWatcher::REQUEST_OPEN, u""_ustr, aRequest.aModule );
1312     AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aViewList, DispatchWatcher::REQUEST_VIEW, u""_ustr, aRequest.aModule );
1313     AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aStartList, DispatchWatcher::REQUEST_START, aRequest.aStartListParams, aRequest.aModule );
1314     AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintList, DispatchWatcher::REQUEST_PRINT, u""_ustr, aRequest.aModule );
1315     AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintToList, DispatchWatcher::REQUEST_PRINTTO, aRequest.aPrinterName, aRequest.aModule );
1316     AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceOpenList, DispatchWatcher::REQUEST_FORCEOPEN, u""_ustr, aRequest.aModule );
1317     AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceNewList, DispatchWatcher::REQUEST_FORCENEW, u""_ustr, aRequest.aModule );
1318     AddConversionsToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aConversionList, aRequest.aConversionParams, aRequest.aPrinterName, aRequest.aModule, aRequest.aConversionOut, aRequest.aImageConversionType, aRequest.bTextCat, aRequest.bScriptCat );
1319     bool bShutdown( false );
1320 
1321     if ( pGlobal.is() )
1322     {
1323         if( ! pGlobal->AreRequestsEnabled() )
1324         {
1325             // Either starting, or downing - do not process the request, just try to bring Office to front
1326             ApplicationEvent* pAppEvent =
1327                 new ApplicationEvent(ApplicationEvent::Type::Appear);
1328             ImplPostForeignAppEvent(pAppEvent);
1329             return bShutdown;
1330         }
1331 
1332         pGlobal->mnPendingRequests += aDispatchList.size();
1333         if ( !pGlobal->mpDispatchWatcher.is() )
1334         {
1335             pGlobal->mpDispatchWatcher = new DispatchWatcher;
1336         }
1337         rtl::Reference<DispatchWatcher> dispatchWatcher(
1338             pGlobal->mpDispatchWatcher);
1339 
1340         // copy for execute
1341         std::vector<DispatchWatcher::DispatchRequest> aTempList;
1342         aTempList.swap( aDispatchList );
1343 
1344         aGuard.clear();
1345 
1346         // Execute dispatch requests
1347         bShutdown = dispatchWatcher->executeDispatchRequests( aTempList, noTerminate);
1348         if (aRequest.mpbSuccess)
1349             *aRequest.mpbSuccess = true; // signal that we have actually succeeded
1350     }
1351 
1352     return bShutdown;
1353 }
1354 
1355 }
1356 
1357 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1358