/* -*- 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 #if HAVE_FEATURE_MACOSX_SANDBOX #include #include #include #endif #include "cmdlineargs.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include 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: :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: */