/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <config_features.h>
#if HAVE_FEATURE_MACOSX_SANDBOX
#include <premac.h>
#include <Foundation/Foundation.h>
#include <postmac.h>
#endif
#include "cmdlineargs.hxx"
#include <osl/thread.hxx>
#include <tools/stream.hxx>
#include <tools/urlobj.hxx>
#include <rtl/ustring.hxx>
#include <rtl/process.h>
#include <comphelper/lok.hxx>
#include <comphelper/processfactory.hxx>
#include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
#include <unotools/bootstrap.hxx>
#include <rtl/strbuf.hxx>
#include <osl/file.hxx>
#include <sal/log.hxx>
#include <mutex>
namespace desktop
{
namespace {
OUString translateExternalUris(OUString const & input) {
OUString t(
css::uri::ExternalUriReferenceTranslator::create(
comphelper::getProcessComponentContext())->
translateToInternal(input));
return t.isEmpty() ? input : t;
}
std::vector< OUString > translateExternalUris(
std::vector< OUString > const & input)
{
std::vector< OUString > t;
t.reserve(input.size());
for (auto const& elem : input)
{
t.push_back(translateExternalUris(elem));
}
return t;
}
class ExtCommandLineSupplier: public CommandLineArgs::Supplier {
public:
explicit ExtCommandLineSupplier():
m_count(
comphelper::LibreOfficeKit::isActive()
? 0 : rtl_getAppCommandArgCount()),
m_index(0)
{
OUString url;
if (utl::Bootstrap::getProcessWorkingDir(url)) {
m_cwdUrl = url;
}
}
virtual std::optional< OUString > getCwdUrl() override { return m_cwdUrl; }
virtual bool next(OUString& argument) override {
if (m_index < m_count) {
rtl_getAppCommandArg(m_index++, &argument.pData);
return true;
} else {
return false;
}
}
private:
std::optional< OUString > m_cwdUrl;
sal_uInt32 m_count;
sal_uInt32 m_index;
};
enum class CommandLineEvent {
Open, Print, View, Start, PrintTo,
ForceOpen, ForceNew, Conversion, BatchPrint
};
// Office URI Schemes: see https://msdn.microsoft.com/en-us/library/dn906146
// This functions checks if the arg is an Office URI.
// If applicable, it updates arg to inner URI.
// If no event argument is explicitly set in command line,
// then it returns updated command line event,
// according to Office URI command.
CommandLineEvent CheckOfficeURI(/* in,out */ OUString& arg, CommandLineEvent curEvt)
{
// 1. Strip the scheme name
OUString rest1;
bool isOfficeURI = ( arg.startsWithIgnoreAsciiCase("vnd.libreoffice.command:", &rest1) // Proposed extended schema
|| arg.startsWithIgnoreAsciiCase("ms-word:", &rest1)
|| arg.startsWithIgnoreAsciiCase("ms-powerpoint:", &rest1)
|| arg.startsWithIgnoreAsciiCase("ms-excel:", &rest1)
|| arg.startsWithIgnoreAsciiCase("ms-visio:", &rest1)
|| arg.startsWithIgnoreAsciiCase("ms-access:", &rest1));
if (!isOfficeURI)
return curEvt;
OUString rest2;
tools::Long nURIlen = -1;
// URL might be encoded
OUString decoded_rest = rest1.replaceAll("%7C", "|").replaceAll("%7c", "|");
// 2. Discriminate by command name (incl. 1st command argument descriptor)
// Extract URI: everything up to possible next argument
if (decoded_rest.startsWith("ofv|u|", &rest2))
{
// Open for view - override only in default mode
if (curEvt == CommandLineEvent::Open)
curEvt = CommandLineEvent::View;
nURIlen = rest2.indexOf("|");
}
else if (decoded_rest.startsWith("ofe|u|", &rest2))
{
// Open for editing - override only in default mode
if (curEvt == CommandLineEvent::Open)
curEvt = CommandLineEvent::ForceOpen;
nURIlen = rest2.indexOf("|");
}
else if (decoded_rest.startsWith("nft|u|", &rest2))
{
// New from template - override only in default mode
if (curEvt == CommandLineEvent::Open)
curEvt = CommandLineEvent::ForceNew;
nURIlen = rest2.indexOf("|");
// TODO: process optional second argument (default save-to location)
// For now, we just ignore it
}
else
{
// Abbreviated scheme: <scheme-name>:URI
// "ofv|u|" implied
// override only in default mode
if (curEvt == CommandLineEvent::Open)
curEvt = CommandLineEvent::View;
rest2 = rest1;
}
if (nURIlen < 0)
nURIlen = rest2.getLength();
auto const uri = rest2.subView(0, nURIlen);
if (INetURLObject(uri).GetProtocol() == INetProtocol::Macro) {
// Let the "Open" machinery process the full command URI (leading to failure, by intention,
// as the "Open" machinery does not know about those command URI schemes):
curEvt = CommandLineEvent::Open;
} else {
arg = uri;
}
return curEvt;
}
// Skip single newline (be it *NIX LF, MacOS CR, of Win CRLF)
// Changes the offset, and returns true if moved
bool SkipNewline(const char* & pStr)
{
if ((*pStr != '\r') && (*pStr != '\n'))
return false;
if (*pStr == '\r')
++pStr;
if (*pStr == '\n')
++pStr;
return true;
}
// Web query: http://support.microsoft.com/kb/157482
CommandLineEvent CheckWebQuery(/* in,out */ OUString& arg, CommandLineEvent curEvt)
{
// Only handle files with extension .iqy
if (!arg.endsWithIgnoreAsciiCase(".iqy"))
return curEvt;
static std::mutex aMutex;
std::lock_guard aGuard(aMutex);
try
{
OUString sFileURL;
// Cannot use translateExternalUris yet, because process service factory is not yet available
if (osl::FileBase::getFileURLFromSystemPath(arg, sFileURL) != osl::FileBase::RC::E_None)
return curEvt;
SvFileStream stream(sFileURL, StreamMode::READ);
const sal_Int32 nBufLen = 32000;
char sBuffer[nBufLen];
size_t nRead = stream.ReadBytes(sBuffer, nBufLen);
if (nRead < 8) // WEB\n1\n...
return curEvt;
const char* pPos = sBuffer;
if (strncmp(pPos, "WEB", 3) != 0)
return curEvt;
pPos += 3;
if (!SkipNewline(pPos))
return curEvt;
if (*pPos != '1')
return curEvt;
++pPos;
if (!SkipNewline(pPos))
return curEvt;
OStringBuffer aResult(nRead);
do
{
const char* pPos1 = pPos;
const char* pEnd = sBuffer + nRead;
while ((pPos1 < pEnd) && (*pPos1 != '\r') && (*pPos1 != '\n'))
++pPos1;
aResult.append(pPos, pPos1 - pPos);
if (pPos1 < pEnd) // newline
break;
pPos = sBuffer;
} while ((nRead = stream.ReadBytes(sBuffer, nBufLen)) > 0);
stream.Close();
arg = OStringToOUString(aResult, osl_getThreadTextEncoding());
return CommandLineEvent::ForceNew;
}
catch (...)
{
SAL_WARN("desktop.app", "An error processing Web Query file: " << arg);
}
return curEvt;
}
} // namespace
CommandLineArgs::Supplier::Exception::Exception() {}
CommandLineArgs::Supplier::Exception::Exception(Exception const &) {}
CommandLineArgs::Supplier::Exception &
CommandLineArgs::Supplier::Exception::operator =(Exception const &)
{ return *this; }
CommandLineArgs::Supplier::~Supplier() {}
// initialize class with command line parameters from process environment
CommandLineArgs::CommandLineArgs()
{
InitParamValues();
ExtCommandLineSupplier s;
ParseCommandLine_Impl( s );
}
CommandLineArgs::CommandLineArgs( Supplier& supplier )
{
InitParamValues();
ParseCommandLine_Impl( supplier );
}
void CommandLineArgs::ParseCommandLine_Impl( Supplier& supplier )
{
m_cwdUrl = supplier.getCwdUrl();
CommandLineEvent eCurrentEvent = CommandLineEvent::Open;
for (;;)
{
OUString aArg;
if ( !supplier.next(aArg) )
{
break;
}
if ( !aArg.isEmpty() )
{
m_bEmpty = false;
OUString oArg;
OUString oDeprecatedArg;
if (!aArg.startsWith("--", &oArg) && aArg.startsWith("-", &oArg)
&& aArg.getLength() > 2) // -h, -?, -n, -o, -p are still valid
{
oDeprecatedArg = aArg; // save here, since aArg can change later
}
OUString rest;
if ( oArg == "minimized" )
{
m_minimized = true;
}
else if ( oArg == "invisible" )
{
m_invisible = true;
}
else if ( oArg == "norestore" )
{
m_norestore = true;
}
else if ( oArg == "nodefault" )
{
m_nodefault = true;
}
else if ( oArg == "headless" )
{
setHeadless();
}
else if ( oArg == "safe-mode" )
{
m_safemode = true;
}
else if ( oArg == "cat" )
{
m_textcat = true;
m_conversionparams = "txt:Text";
eCurrentEvent = CommandLineEvent::Conversion;
setHeadless();
}
else if ( oArg == "script-cat" )
{
m_scriptcat = true;
eCurrentEvent = CommandLineEvent::Conversion;
setHeadless();
}
else if ( oArg == "quickstart" )
{
#if defined(ENABLE_QUICKSTART_APPLET)
m_quickstart = true;
#endif
m_noquickstart = false;
}
else if ( oArg == "quickstart=no" )
{
m_noquickstart = true;
m_quickstart = false;
}
else if ( oArg == "terminate_after_init" )
{
m_terminateafterinit = true;
}
else if ( oArg == "nofirststartwizard" )
{
// Do nothing, accept only for backward compatibility
}
else if ( oArg == "nologo" )
{
m_nologo = true;
}
#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
else if ( oArg == "nolockcheck" )
{
m_nolockcheck = true;
}
#endif
else if ( oArg == "help" || aArg == "-h" || aArg == "-?" )
{
m_help = true;
}
else if ( oArg == "helpwriter" )
{
m_helpwriter = true;
}
else if ( oArg == "helpcalc" )
{
m_helpcalc = true;
}
else if ( oArg == "helpdraw" )
{
m_helpdraw = true;
}
else if ( oArg == "helpimpress" )
{
m_helpimpress = true;
}
else if ( oArg == "helpbase" )
{
m_helpbase = true;
}
else if ( oArg == "helpbasic" )
{
m_helpbasic = true;
}
else if ( oArg == "helpmath" )
{
m_helpmath = true;
}
else if ( oArg == "protector" )
{
// Not relevant for us here, but can be used in unit tests.
// Usually unit tests would not end up here, but e.g. the
// LOK Tiled Rendering tests end up running a full soffice
// process, and we can't bail on the use of --protector.
// We specifically need to consume the following 2 arguments
// for --protector
if ((!supplier.next(aArg) || !supplier.next(aArg)) && m_unknown.isEmpty())
m_unknown = "--protector must be followed by two arguments";
}
else if ( oArg == "version" )
{
m_version = true;
}
else if ( oArg.startsWith("splash-pipe=") )
{
m_splashpipe = true;
}
#ifdef MACOSX
/* #i84053# ignore -psn on Mac
Platform dependent #ifdef here is ugly, however this is currently
the only platform dependent parameter. Should more appear
we should find a better solution
*/
else if ( aArg.startsWith("-psn") )
{
oDeprecatedArg.clear();
}
#endif
#if HAVE_FEATURE_MACOSX_SANDBOX
else if ( oArg == "nstemporarydirectory" )
{
printf("%s\n", [NSTemporaryDirectory() UTF8String]);
exit(0);
}
#endif
#ifdef _WIN32
/* fdo#57203 ignore -Embedding on Windows
when LibreOffice is launched by COM+
*/
else if ( oArg == "Embedding" )
{
oDeprecatedArg.clear();
}
#endif
else if ( oArg.startsWith("infilter=", &rest))
{
m_infilter.push_back(rest);
}
else if ( oArg.startsWith("accept=", &rest))
{
m_accept.push_back(rest);
}
else if ( oArg.startsWith("unaccept=", &rest))
{
m_unaccept.push_back(rest);
}
else if ( oArg.startsWith("language=", &rest))
{
m_language = rest;
}
else if ( oArg.startsWith("pidfile=", &rest))
{
m_pidfile = rest;
}
else if ( oArg == "writer" )
{
m_writer = true;
m_bDocumentArgs = true;
}
else if ( oArg == "calc" )
{
m_calc = true;
m_bDocumentArgs = true;
}
else if ( oArg == "draw" )
{
m_draw = true;
m_bDocumentArgs = true;
}
else if ( oArg == "impress" )
{
m_impress = true;
m_bDocumentArgs = true;
}
else if ( oArg == "base" )
{
m_base = true;
m_bDocumentArgs = true;
}
else if ( oArg == "global" )
{
m_global = true;
m_bDocumentArgs = true;
}
else if ( oArg == "math" )
{
m_math = true;
m_bDocumentArgs = true;
}
else if ( oArg == "web" )
{
m_web = true;
m_bDocumentArgs = true;
}
else if ( aArg == "-n" )
{
// force new documents based on the following documents
eCurrentEvent = CommandLineEvent::ForceNew;
}
else if ( aArg == "-o" )
{
// force open documents regardless if they are templates or not
eCurrentEvent = CommandLineEvent::ForceOpen;
}
else if ( oArg == "pt" )
{
// Print to special printer
eCurrentEvent = CommandLineEvent::PrintTo;
// first argument after "-pt" must be the printer name
if (supplier.next(aArg))
m_printername = aArg;
else if (m_unknown.isEmpty())
m_unknown = "--pt must be followed by printername";
}
else if ( aArg == "-p" )
{
// Print to default printer
eCurrentEvent = CommandLineEvent::Print;
}
else if ( oArg == "view")
{
// open in viewmode
eCurrentEvent = CommandLineEvent::View;
}
else if (oArg == "show" || oArg.startsWith("show=", &rest))
{
// open in viewmode
eCurrentEvent = CommandLineEvent::Start;
// start on the first slide unless a valid starting slide # was provided
m_startListParams = rest.toUInt32() > 0 ? rest : u"1"_ustr;
}
else if ( oArg == "display" )
{
// The command line argument following --display should
// always be treated as the argument of --display.
// --display and its argument are handled "out of line"
// in Unix-only desktop/unx/source/splashx.c and vcl/unx/*,
// and just ignored here
(void)supplier.next(aArg);
}
else if ( oArg == "convert-to" )
{
eCurrentEvent = CommandLineEvent::Conversion;
// first argument must be the params
if (supplier.next(aArg))
{
m_conversionparams = aArg;
// It doesn't make sense to use convert-to without headless.
setHeadless();
}
else if (m_unknown.isEmpty())
m_unknown = "--convert-to must be followed by output_file_extension[:output_filter_name]";
}
else if ( oArg == "print-to-file" )
{
eCurrentEvent = CommandLineEvent::BatchPrint;
}
else if ( oArg == "printer-name" )
{
if (eCurrentEvent == CommandLineEvent::BatchPrint)
{
// first argument is the printer name
if (supplier.next(aArg))
m_printername = aArg;
else if (m_unknown.isEmpty())
m_unknown = "--printer-name must be followed by printername";
}
else if (m_unknown.isEmpty())
{
m_unknown = "--printer-name must directly follow --print-to-file";
}
}
else if ( oArg == "outdir" )
{
if (eCurrentEvent == CommandLineEvent::Conversion ||
eCurrentEvent == CommandLineEvent::BatchPrint)
{
if (supplier.next(aArg))
m_conversionout = aArg;
else if (m_unknown.isEmpty())
m_unknown = "--outdir must be followed by output directory path";
}
else if (m_unknown.isEmpty())
{
m_unknown = "--outdir must directly follow either output filter specification of --convert-to, or --print-to-file or its printer specification";
}
}
else if ( eCurrentEvent == CommandLineEvent::Conversion
&& oArg == "convert-images-to" )
{
if (supplier.next(aArg))
m_convertimages = aArg;
else if (m_unknown.isEmpty())
m_unknown = "--convert-images-to must be followed by an image type";
}
else if ( aArg.startsWith("-") )
{
// because it's impossible to filter these options that
// are handled in the soffice shell script with the
// primitive tools that /bin/sh offers, ignore them here
if (
#if defined UNX
oArg != "record" &&
oArg != "backtrace" &&
oArg != "strace" &&
oArg != "valgrind" &&
// for X Session Management, handled in
// vcl/unx/generic/app/sm.cxx:
oArg != "session=" &&
#endif
//ignore additional legacy options that don't do anything anymore
oArg != "nocrashreport" &&
m_unknown.isEmpty())
{
m_unknown = aArg;
}
oDeprecatedArg.clear();
}
else
{
// handle this argument as a filename
// First check if this is an Office URI
// This will possibly adjust event for this argument
// and put real URI to aArg
CommandLineEvent eThisEvent = CheckOfficeURI(aArg, eCurrentEvent);
// Now check if this is a Web Query file
eThisEvent = CheckWebQuery(aArg, eThisEvent);
switch (eThisEvent)
{
case CommandLineEvent::Open:
m_openlist.push_back(aArg);
m_bDocumentArgs = true;
break;
case CommandLineEvent::View:
m_viewlist.push_back(aArg);
m_bDocumentArgs = true;
break;
case CommandLineEvent::Start:
m_startlist.push_back(aArg);
m_bDocumentArgs = true;
break;
case CommandLineEvent::Print:
m_printlist.push_back(aArg);
m_bDocumentArgs = true;
break;
case CommandLineEvent::PrintTo:
m_printtolist.push_back(aArg);
m_bDocumentArgs = true;
break;
case CommandLineEvent::ForceNew:
m_forcenewlist.push_back(aArg);
m_bDocumentArgs = true;
break;
case CommandLineEvent::ForceOpen:
m_forceopenlist.push_back(aArg);
m_bDocumentArgs = true;
break;
case CommandLineEvent::Conversion:
case CommandLineEvent::BatchPrint:
m_conversionlist.push_back(aArg);
break;
}
}
if (!oDeprecatedArg.isEmpty())
{
OString sArg(OUStringToOString(oDeprecatedArg, osl_getThreadTextEncoding()));
fprintf(stderr, "Warning: %s is deprecated. Use -%s instead.\n", sArg.getStr(), sArg.getStr());
}
}
}
}
void CommandLineArgs::InitParamValues()
{
m_minimized = false;
m_norestore = false;
#if HAVE_FEATURE_UI
m_invisible = false;
m_headless = false;
#else
m_invisible = true;
m_headless = true;
#endif
m_quickstart = false;
m_noquickstart = false;
m_terminateafterinit = false;
m_nologo = false;
m_nolockcheck = false;
m_nodefault = false;
m_help = false;
m_writer = false;
m_calc = false;
m_draw = false;
m_impress = false;
m_global = false;
m_math = false;
m_web = false;
m_base = false;
m_helpwriter = false;
m_helpcalc = false;
m_helpdraw = false;
m_helpbasic = false;
m_helpmath = false;
m_helpimpress = false;
m_helpbase = false;
m_version = false;
m_splashpipe = false;
m_bEmpty = true;
m_bDocumentArgs = false;
m_textcat = false;
m_scriptcat = false;
m_safemode = false;
}
bool CommandLineArgs::HasModuleParam() const
{
return m_writer || m_calc || m_draw || m_impress || m_global || m_math
|| m_web || m_base;
}
void CommandLineArgs::RemoveFilesFromOpenListEndingWith(const OUString& rExt)
{
std::erase_if(m_openlist, [rExt](OUString url) { return url.endsWithIgnoreAsciiCase(rExt); });
}
std::vector< OUString > CommandLineArgs::GetOpenList() const
{
return translateExternalUris(m_openlist);
}
std::vector< OUString > CommandLineArgs::GetViewList() const
{
return translateExternalUris(m_viewlist);
}
std::vector< OUString > CommandLineArgs::GetStartList() const
{
return translateExternalUris(m_startlist);
}
std::vector< OUString > CommandLineArgs::GetForceOpenList() const
{
return translateExternalUris(m_forceopenlist);
}
std::vector< OUString > CommandLineArgs::GetForceNewList() const
{
return translateExternalUris(m_forcenewlist);
}
std::vector< OUString > CommandLineArgs::GetPrintList() const
{
return translateExternalUris(m_printlist);
}
std::vector< OUString > CommandLineArgs::GetPrintToList() const
{
return translateExternalUris(m_printtolist);
}
std::vector< OUString > CommandLineArgs::GetConversionList() const
{
return translateExternalUris(m_conversionlist);
}
OUString CommandLineArgs::GetConversionOut() const
{
return translateExternalUris(m_conversionout);
}
} // namespace desktop
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V501 There are identical sub-expressions '!supplier.next(aArg)' to the left and to the right of the '||' operator.
↑ V530 The return value of function 'append' is required to be utilized.