1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <config_features.h>
22 #if HAVE_FEATURE_MACOSX_SANDBOX
24 #include <Foundation/Foundation.h>
28 #include "cmdlineargs.hxx"
29 #include <osl/thread.hxx>
30 #include <tools/stream.hxx>
31 #include <tools/urlobj.hxx>
32 #include <rtl/ustring.hxx>
33 #include <rtl/process.h>
34 #include <comphelper/lok.hxx>
35 #include <comphelper/processfactory.hxx>
36 #include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
37 #include <unotools/bootstrap.hxx>
39 #include <rtl/strbuf.hxx>
40 #include <osl/file.hxx>
41 #include <sal/log.hxx>
50 OUString
translateExternalUris(OUString
const & input
) {
52 css::uri::ExternalUriReferenceTranslator::create(
53 comphelper::getProcessComponentContext())->
54 translateToInternal(input
));
55 return t
.isEmpty() ? input
: t
;
58 std::vector
< OUString
> translateExternalUris(
59 std::vector
< OUString
> const & input
)
61 std::vector
< OUString
> t
;
62 t
.reserve(input
.size());
63 for (auto const& elem
: input
)
65 t
.push_back(translateExternalUris(elem
));
70 class ExtCommandLineSupplier
: public CommandLineArgs::Supplier
{
72 explicit ExtCommandLineSupplier():
74 comphelper::LibreOfficeKit::isActive()
75 ? 0 : rtl_getAppCommandArgCount()),
79 if (utl::Bootstrap::getProcessWorkingDir(url
)) {
84 virtual std::optional
< OUString
> getCwdUrl() override
{ return m_cwdUrl
; }
86 virtual bool next(OUString
& argument
) override
{
87 if (m_index
< m_count
) {
88 rtl_getAppCommandArg(m_index
++, &argument
.pData
);
96 std::optional
< OUString
> m_cwdUrl
;
101 enum class CommandLineEvent
{
102 Open
, Print
, View
, Start
, PrintTo
,
103 ForceOpen
, ForceNew
, Conversion
, BatchPrint
106 // Office URI Schemes: see https://msdn.microsoft.com/en-us/library/dn906146
107 // This functions checks if the arg is an Office URI.
108 // If applicable, it updates arg to inner URI.
109 // If no event argument is explicitly set in command line,
110 // then it returns updated command line event,
111 // according to Office URI command.
112 CommandLineEvent
CheckOfficeURI(/* in,out */ OUString
& arg
, CommandLineEvent curEvt
)
114 // 1. Strip the scheme name
116 bool isOfficeURI
= ( arg
.startsWithIgnoreAsciiCase("vnd.libreoffice.command:", &rest1
) // Proposed extended schema
117 || arg
.startsWithIgnoreAsciiCase("ms-word:", &rest1
)
118 || arg
.startsWithIgnoreAsciiCase("ms-powerpoint:", &rest1
)
119 || arg
.startsWithIgnoreAsciiCase("ms-excel:", &rest1
)
120 || arg
.startsWithIgnoreAsciiCase("ms-visio:", &rest1
)
121 || arg
.startsWithIgnoreAsciiCase("ms-access:", &rest1
));
126 tools::Long nURIlen
= -1;
128 // URL might be encoded
129 OUString decoded_rest
= rest1
.replaceAll("%7C", "|").replaceAll("%7c", "|");
131 // 2. Discriminate by command name (incl. 1st command argument descriptor)
132 // Extract URI: everything up to possible next argument
133 if (decoded_rest
.startsWith("ofv|u|", &rest2
))
135 // Open for view - override only in default mode
136 if (curEvt
== CommandLineEvent::Open
)
137 curEvt
= CommandLineEvent::View
;
138 nURIlen
= rest2
.indexOf("|");
140 else if (decoded_rest
.startsWith("ofe|u|", &rest2
))
142 // Open for editing - override only in default mode
143 if (curEvt
== CommandLineEvent::Open
)
144 curEvt
= CommandLineEvent::ForceOpen
;
145 nURIlen
= rest2
.indexOf("|");
147 else if (decoded_rest
.startsWith("nft|u|", &rest2
))
149 // New from template - override only in default mode
150 if (curEvt
== CommandLineEvent::Open
)
151 curEvt
= CommandLineEvent::ForceNew
;
152 nURIlen
= rest2
.indexOf("|");
153 // TODO: process optional second argument (default save-to location)
154 // For now, we just ignore it
158 // Abbreviated scheme: <scheme-name>:URI
160 // override only in default mode
161 if (curEvt
== CommandLineEvent::Open
)
162 curEvt
= CommandLineEvent::View
;
166 nURIlen
= rest2
.getLength();
167 auto const uri
= rest2
.subView(0, nURIlen
);
168 if (INetURLObject(uri
).GetProtocol() == INetProtocol::Macro
) {
169 // Let the "Open" machinery process the full command URI (leading to failure, by intention,
170 // as the "Open" machinery does not know about those command URI schemes):
171 curEvt
= CommandLineEvent::Open
;
178 // Skip single newline (be it *NIX LF, MacOS CR, of Win CRLF)
179 // Changes the offset, and returns true if moved
180 bool SkipNewline(const char* & pStr
)
182 if ((*pStr
!= '\r') && (*pStr
!= '\n'))
191 // Web query: http://support.microsoft.com/kb/157482
192 CommandLineEvent
CheckWebQuery(/* in,out */ OUString
& arg
, CommandLineEvent curEvt
)
194 // Only handle files with extension .iqy
195 if (!arg
.endsWithIgnoreAsciiCase(".iqy"))
198 static std::mutex aMutex
;
199 std::lock_guard
aGuard(aMutex
);
204 // Cannot use translateExternalUris yet, because process service factory is not yet available
205 if (osl::FileBase::getFileURLFromSystemPath(arg
, sFileURL
) != osl::FileBase::RC::E_None
)
207 SvFileStream
stream(sFileURL
, StreamMode::READ
);
209 const sal_Int32 nBufLen
= 32000;
210 char sBuffer
[nBufLen
];
211 size_t nRead
= stream
.ReadBytes(sBuffer
, nBufLen
);
212 if (nRead
< 8) // WEB\n1\n...
215 const char* pPos
= sBuffer
;
216 if (strncmp(pPos
, "WEB", 3) != 0)
219 if (!SkipNewline(pPos
))
224 if (!SkipNewline(pPos
))
227 OStringBuffer
aResult(nRead
);
230 const char* pPos1
= pPos
;
231 const char* pEnd
= sBuffer
+ nRead
;
232 while ((pPos1
< pEnd
) && (*pPos1
!= '\r') && (*pPos1
!= '\n'))
234 aResult
.append(pPos
, pPos1
- pPos
);
235 if (pPos1
< pEnd
) // newline
238 } while ((nRead
= stream
.ReadBytes(sBuffer
, nBufLen
)) > 0);
242 arg
= OStringToOUString(aResult
, osl_getThreadTextEncoding());
243 return CommandLineEvent::ForceNew
;
247 SAL_WARN("desktop.app", "An error processing Web Query file: " << arg
);
255 CommandLineArgs::Supplier::Exception::Exception() {}
257 CommandLineArgs::Supplier::Exception::Exception(Exception
const &) {}
259 CommandLineArgs::Supplier::Exception
&
260 CommandLineArgs::Supplier::Exception::operator =(Exception
const &)
263 CommandLineArgs::Supplier::~Supplier() {}
265 // initialize class with command line parameters from process environment
266 CommandLineArgs::CommandLineArgs()
269 ExtCommandLineSupplier s
;
270 ParseCommandLine_Impl( s
);
273 CommandLineArgs::CommandLineArgs( Supplier
& supplier
)
276 ParseCommandLine_Impl( supplier
);
279 void CommandLineArgs::ParseCommandLine_Impl( Supplier
& supplier
)
281 m_cwdUrl
= supplier
.getCwdUrl();
282 CommandLineEvent eCurrentEvent
= CommandLineEvent::Open
;
287 if ( !supplier
.next(aArg
) )
292 if ( !aArg
.isEmpty() )
296 OUString oDeprecatedArg
;
297 if (!aArg
.startsWith("--", &oArg
) && aArg
.startsWith("-", &oArg
)
298 && aArg
.getLength() > 2) // -h, -?, -n, -o, -p are still valid
300 oDeprecatedArg
= aArg
; // save here, since aArg can change later
304 if ( oArg
== "minimized" )
308 else if ( oArg
== "invisible" )
312 else if ( oArg
== "norestore" )
316 else if ( oArg
== "nodefault" )
320 else if ( oArg
== "headless" )
324 else if ( oArg
== "safe-mode" )
328 else if ( oArg
== "cat" )
331 m_conversionparams
= "txt:Text";
332 eCurrentEvent
= CommandLineEvent::Conversion
;
335 else if ( oArg
== "script-cat" )
338 eCurrentEvent
= CommandLineEvent::Conversion
;
341 else if ( oArg
== "quickstart" )
343 #if defined(ENABLE_QUICKSTART_APPLET)
346 m_noquickstart
= false;
348 else if ( oArg
== "quickstart=no" )
350 m_noquickstart
= true;
351 m_quickstart
= false;
353 else if ( oArg
== "terminate_after_init" )
355 m_terminateafterinit
= true;
357 else if ( oArg
== "nofirststartwizard" )
359 // Do nothing, accept only for backward compatibility
361 else if ( oArg
== "nologo" )
365 #if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
366 else if ( oArg
== "nolockcheck" )
368 m_nolockcheck
= true;
371 else if ( oArg
== "help" || aArg
== "-h" || aArg
== "-?" )
375 else if ( oArg
== "helpwriter" )
379 else if ( oArg
== "helpcalc" )
383 else if ( oArg
== "helpdraw" )
387 else if ( oArg
== "helpimpress" )
389 m_helpimpress
= true;
391 else if ( oArg
== "helpbase" )
395 else if ( oArg
== "helpbasic" )
399 else if ( oArg
== "helpmath" )
403 else if ( oArg
== "protector" )
405 // Not relevant for us here, but can be used in unit tests.
406 // Usually unit tests would not end up here, but e.g. the
407 // LOK Tiled Rendering tests end up running a full soffice
408 // process, and we can't bail on the use of --protector.
410 // We specifically need to consume the following 2 arguments
412 if ((!supplier
.next(aArg
) || !supplier
.next(aArg
)) && m_unknown
.isEmpty())
413 m_unknown
= "--protector must be followed by two arguments";
415 else if ( oArg
== "version" )
419 else if ( oArg
.startsWith("splash-pipe=") )
424 /* #i84053# ignore -psn on Mac
425 Platform dependent #ifdef here is ugly, however this is currently
426 the only platform dependent parameter. Should more appear
427 we should find a better solution
429 else if ( aArg
.startsWith("-psn") )
431 oDeprecatedArg
.clear();
434 #if HAVE_FEATURE_MACOSX_SANDBOX
435 else if ( oArg
== "nstemporarydirectory" )
437 printf("%s\n", [NSTemporaryDirectory() UTF8String
]);
442 /* fdo#57203 ignore -Embedding on Windows
443 when LibreOffice is launched by COM+
445 else if ( oArg
== "Embedding" )
447 oDeprecatedArg
.clear();
450 else if ( oArg
.startsWith("infilter=", &rest
))
452 m_infilter
.push_back(rest
);
454 else if ( oArg
.startsWith("accept=", &rest
))
456 m_accept
.push_back(rest
);
458 else if ( oArg
.startsWith("unaccept=", &rest
))
460 m_unaccept
.push_back(rest
);
462 else if ( oArg
.startsWith("language=", &rest
))
466 else if ( oArg
.startsWith("pidfile=", &rest
))
470 else if ( oArg
== "writer" )
473 m_bDocumentArgs
= true;
475 else if ( oArg
== "calc" )
478 m_bDocumentArgs
= true;
480 else if ( oArg
== "draw" )
483 m_bDocumentArgs
= true;
485 else if ( oArg
== "impress" )
488 m_bDocumentArgs
= true;
490 else if ( oArg
== "base" )
493 m_bDocumentArgs
= true;
495 else if ( oArg
== "global" )
498 m_bDocumentArgs
= true;
500 else if ( oArg
== "math" )
503 m_bDocumentArgs
= true;
505 else if ( oArg
== "web" )
508 m_bDocumentArgs
= true;
510 else if ( aArg
== "-n" )
512 // force new documents based on the following documents
513 eCurrentEvent
= CommandLineEvent::ForceNew
;
515 else if ( aArg
== "-o" )
517 // force open documents regardless if they are templates or not
518 eCurrentEvent
= CommandLineEvent::ForceOpen
;
520 else if ( oArg
== "pt" )
522 // Print to special printer
523 eCurrentEvent
= CommandLineEvent::PrintTo
;
524 // first argument after "-pt" must be the printer name
525 if (supplier
.next(aArg
))
526 m_printername
= aArg
;
527 else if (m_unknown
.isEmpty())
528 m_unknown
= "--pt must be followed by printername";
530 else if ( aArg
== "-p" )
532 // Print to default printer
533 eCurrentEvent
= CommandLineEvent::Print
;
535 else if ( oArg
== "view")
538 eCurrentEvent
= CommandLineEvent::View
;
540 else if (oArg
== "show" || oArg
.startsWith("show=", &rest
))
543 eCurrentEvent
= CommandLineEvent::Start
;
544 // start on the first slide unless a valid starting slide # was provided
545 m_startListParams
= rest
.toUInt32() > 0 ? rest
: u
"1"_ustr
;
547 else if ( oArg
== "display" )
549 // The command line argument following --display should
550 // always be treated as the argument of --display.
551 // --display and its argument are handled "out of line"
552 // in Unix-only desktop/unx/source/splashx.c and vcl/unx/*,
553 // and just ignored here
554 (void)supplier
.next(aArg
);
556 else if ( oArg
== "convert-to" )
558 eCurrentEvent
= CommandLineEvent::Conversion
;
559 // first argument must be the params
560 if (supplier
.next(aArg
))
562 m_conversionparams
= aArg
;
563 // It doesn't make sense to use convert-to without headless.
566 else if (m_unknown
.isEmpty())
567 m_unknown
= "--convert-to must be followed by output_file_extension[:output_filter_name]";
569 else if ( oArg
== "print-to-file" )
571 eCurrentEvent
= CommandLineEvent::BatchPrint
;
573 else if ( oArg
== "printer-name" )
575 if (eCurrentEvent
== CommandLineEvent::BatchPrint
)
577 // first argument is the printer name
578 if (supplier
.next(aArg
))
579 m_printername
= aArg
;
580 else if (m_unknown
.isEmpty())
581 m_unknown
= "--printer-name must be followed by printername";
583 else if (m_unknown
.isEmpty())
585 m_unknown
= "--printer-name must directly follow --print-to-file";
588 else if ( oArg
== "outdir" )
590 if (eCurrentEvent
== CommandLineEvent::Conversion
||
591 eCurrentEvent
== CommandLineEvent::BatchPrint
)
593 if (supplier
.next(aArg
))
594 m_conversionout
= aArg
;
595 else if (m_unknown
.isEmpty())
596 m_unknown
= "--outdir must be followed by output directory path";
598 else if (m_unknown
.isEmpty())
600 m_unknown
= "--outdir must directly follow either output filter specification of --convert-to, or --print-to-file or its printer specification";
603 else if ( eCurrentEvent
== CommandLineEvent::Conversion
604 && oArg
== "convert-images-to" )
606 if (supplier
.next(aArg
))
607 m_convertimages
= aArg
;
608 else if (m_unknown
.isEmpty())
609 m_unknown
= "--convert-images-to must be followed by an image type";
611 else if ( aArg
.startsWith("-") )
613 // because it's impossible to filter these options that
614 // are handled in the soffice shell script with the
615 // primitive tools that /bin/sh offers, ignore them here
619 oArg
!= "backtrace" &&
621 oArg
!= "valgrind" &&
622 // for X Session Management, handled in
623 // vcl/unx/generic/app/sm.cxx:
624 oArg
!= "session=" &&
626 //ignore additional legacy options that don't do anything anymore
627 oArg
!= "nocrashreport" &&
632 oDeprecatedArg
.clear();
636 // handle this argument as a filename
638 // First check if this is an Office URI
639 // This will possibly adjust event for this argument
640 // and put real URI to aArg
641 CommandLineEvent eThisEvent
= CheckOfficeURI(aArg
, eCurrentEvent
);
643 // Now check if this is a Web Query file
644 eThisEvent
= CheckWebQuery(aArg
, eThisEvent
);
648 case CommandLineEvent::Open
:
649 m_openlist
.push_back(aArg
);
650 m_bDocumentArgs
= true;
652 case CommandLineEvent::View
:
653 m_viewlist
.push_back(aArg
);
654 m_bDocumentArgs
= true;
656 case CommandLineEvent::Start
:
657 m_startlist
.push_back(aArg
);
658 m_bDocumentArgs
= true;
660 case CommandLineEvent::Print
:
661 m_printlist
.push_back(aArg
);
662 m_bDocumentArgs
= true;
664 case CommandLineEvent::PrintTo
:
665 m_printtolist
.push_back(aArg
);
666 m_bDocumentArgs
= true;
668 case CommandLineEvent::ForceNew
:
669 m_forcenewlist
.push_back(aArg
);
670 m_bDocumentArgs
= true;
672 case CommandLineEvent::ForceOpen
:
673 m_forceopenlist
.push_back(aArg
);
674 m_bDocumentArgs
= true;
676 case CommandLineEvent::Conversion
:
677 case CommandLineEvent::BatchPrint
:
678 m_conversionlist
.push_back(aArg
);
683 if (!oDeprecatedArg
.isEmpty())
685 OString
sArg(OUStringToOString(oDeprecatedArg
, osl_getThreadTextEncoding()));
686 fprintf(stderr
, "Warning: %s is deprecated. Use -%s instead.\n", sArg
.getStr(), sArg
.getStr());
692 void CommandLineArgs::InitParamValues()
703 m_quickstart
= false;
704 m_noquickstart
= false;
705 m_terminateafterinit
= false;
707 m_nolockcheck
= false;
718 m_helpwriter
= false;
723 m_helpimpress
= false;
726 m_splashpipe
= false;
728 m_bDocumentArgs
= false;
734 bool CommandLineArgs::HasModuleParam() const
736 return m_writer
|| m_calc
|| m_draw
|| m_impress
|| m_global
|| m_math
740 void CommandLineArgs::RemoveFilesFromOpenListEndingWith(const OUString
& rExt
)
742 std::erase_if(m_openlist
, [rExt
](OUString url
) { return url
.endsWithIgnoreAsciiCase(rExt
); });
745 std::vector
< OUString
> CommandLineArgs::GetOpenList() const
747 return translateExternalUris(m_openlist
);
750 std::vector
< OUString
> CommandLineArgs::GetViewList() const
752 return translateExternalUris(m_viewlist
);
755 std::vector
< OUString
> CommandLineArgs::GetStartList() const
757 return translateExternalUris(m_startlist
);
760 std::vector
< OUString
> CommandLineArgs::GetForceOpenList() const
762 return translateExternalUris(m_forceopenlist
);
765 std::vector
< OUString
> CommandLineArgs::GetForceNewList() const
767 return translateExternalUris(m_forcenewlist
);
770 std::vector
< OUString
> CommandLineArgs::GetPrintList() const
772 return translateExternalUris(m_printlist
);
775 std::vector
< OUString
> CommandLineArgs::GetPrintToList() const
777 return translateExternalUris(m_printtolist
);
780 std::vector
< OUString
> CommandLineArgs::GetConversionList() const
782 return translateExternalUris(m_conversionlist
);
785 OUString
CommandLineArgs::GetConversionOut() const
787 return translateExternalUris(m_conversionout
);
790 } // namespace desktop
792 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */