LibreOffice Module shell (master) 1
shellexec.cxx
Go to the documentation of this file.
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 <osl/thread.h>
21#include <osl/file.hxx>
22#include <rtl/strbuf.hxx>
23#include <sal/log.hxx>
24
25#include "shellexec.hxx"
26#include <com/sun/star/system/SystemShellExecuteException.hpp>
27#include <com/sun/star/system/SystemShellExecuteFlags.hpp>
28
29#include <com/sun/star/lang/IllegalArgumentException.hpp>
30#include <com/sun/star/security/AccessControlException.hpp>
31#include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
32#include <com/sun/star/uri/UriReferenceFactory.hpp>
34#include <comphelper/lok.hxx>
35
36#include <string.h>
37#include <errno.h>
38
39#if defined MACOSX
40#include <sys/stat.h>
41#endif
42
43#ifdef EMSCRIPTEN
44#include <rtl/uri.hxx>
45extern void execute_browser(const char* sUrl);
46#endif
47
48using com::sun::star::system::XSystemShellExecute;
49using com::sun::star::system::SystemShellExecuteException;
50
51using namespace ::com::sun::star::uno;
52using namespace ::com::sun::star::lang;
53using namespace ::com::sun::star::system::SystemShellExecuteFlags;
54using namespace cppu;
55
56#ifndef EMSCRIPTEN
57namespace
58{
59 void escapeForShell( OStringBuffer & rBuffer, const OString & rURL)
60 {
61 sal_Int32 nmax = rURL.getLength();
62 for(sal_Int32 n=0; n < nmax; ++n)
63 {
64 // escape every non alpha numeric characters (excluding a few "known good") by prepending a '\'
65 char c = rURL[n];
66 if( ( c < 'A' || c > 'Z' ) && ( c < 'a' || c > 'z' ) && ( c < '0' || c > '9' ) && c != '/' && c != '.' )
67 rBuffer.append( '\\' );
68
69 rBuffer.append( c );
70 }
71 }
72}
73#endif
74
75ShellExec::ShellExec( const Reference< XComponentContext >& xContext ) :
76 m_xContext(xContext)
77{
78}
79
80void SAL_CALL ShellExec::execute( const OUString& aCommand, const OUString& aParameter, sal_Int32 nFlags )
81{
82#ifndef EMSCRIPTEN
83 OStringBuffer aBuffer, aLaunchBuffer;
84
86 {
87 SAL_WARN("shell", "Unusual - shell attempt to launch " << aCommand << " with params " << aParameter << " under lok");
88 return;
89 }
90
91 // DESKTOP_LAUNCH, see http://freedesktop.org/pipermail/xdg/2004-August/004489.html
92 static const char *pDesktopLaunch = getenv( "DESKTOP_LAUNCH" );
93
94 // Check whether aCommand contains an absolute URI reference:
95 css::uno::Reference< css::uri::XUriReference > uri(
96 css::uri::UriReferenceFactory::create(m_xContext)->parse(aCommand));
97 if (uri.is() && uri->isAbsolute())
98 {
99 // It seems to be a URL...
100 // We need to re-encode file urls because osl_getFileURLFromSystemPath converts
101 // to UTF-8 before encoding non ascii characters, which is not what other apps
102 // expect.
103 OUString aURL = css::uri::ExternalUriReferenceTranslator::create(
104 m_xContext)->translateToExternal(aCommand);
105 if ( aURL.isEmpty() && !aCommand.isEmpty() )
106 {
107 throw RuntimeException(
108 "Cannot translate URI reference to external format: "
109 + aCommand,
110 getXWeak());
111 }
112
113#ifdef MACOSX
114 bool dir = false;
115 if (uri->getScheme().equalsIgnoreAsciiCase("file")) {
116 OUString pathname;
117 auto const e1 = osl::FileBase::getSystemPathFromFileURL(aCommand, pathname);
118 if (e1 != osl::FileBase::E_None) {
119 throw css::lang::IllegalArgumentException(
120 ("XSystemShellExecute.execute, getSystemPathFromFileURL <" + aCommand
121 + "> failed with " + OUString::number(e1)),
122 {}, 0);
123 }
124 OString pathname8;
125 if (!pathname.convertToString(
126 &pathname8, RTL_TEXTENCODING_UTF8,
127 (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR
128 | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
129 {
130 throw css::lang::IllegalArgumentException(
131 "XSystemShellExecute.execute, cannot convert \"" + pathname + "\" to UTF-8", {},
132 0);
133 }
134 struct stat st;
135 auto const e2 = lstat(pathname8.getStr(), &st);
136 if (e2 != 0) {
137 auto const e3 = errno;
138 SAL_INFO("shell", "lstat(" << pathname8 << ") failed with errno " << e3);
139 }
140 if (e2 != 0) {
141 throw css::lang::IllegalArgumentException(
142 "XSystemShellExecute.execute, cannot process <" + aCommand + ">", {}, 0);
143 } else if (S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) {
144 dir = true;
145 } else if ((nFlags & css::system::SystemShellExecuteFlags::URIS_ONLY) != 0
146 && (!S_ISREG(st.st_mode)
147 || (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0))
148 {
149 throw css::security::AccessControlException(
150 "XSystemShellExecute.execute, bad <" + aCommand + ">", {}, {});
151 } else if (pathname.endsWithIgnoreAsciiCase(".class")
152 || pathname.endsWithIgnoreAsciiCase(".dmg")
153 || pathname.endsWithIgnoreAsciiCase(".fileloc")
154 || pathname.endsWithIgnoreAsciiCase(".inetloc")
155 || pathname.endsWithIgnoreAsciiCase(".ipa")
156 || pathname.endsWithIgnoreAsciiCase(".jar")
157 || pathname.endsWithIgnoreAsciiCase(".terminal"))
158 {
159 dir = true;
160 }
161 }
162
163 //TODO: Using open(1) with an argument that syntactically is an absolute
164 // URI reference does not necessarily give expected results:
165 // 1 If the given URI reference matches a supported scheme (e.g.,
166 // "mailto:foo"):
167 // 1.1 If it matches an existing pathname (relative to CWD): Results
168 // in "mailto:foo?\n[0]\tcancel\n[1]\tOpen the file\tmailto:foo\n[2]\t
169 // Open the URL\tmailto:foo\n\nWhich did you mean? Cancelled." on
170 // stderr and SystemShellExecuteException.
171 // 1.2 If it does not match an exitsting pathname (relative to CWD):
172 // Results in the corresponding application being opened with the given
173 // document (e.g., Mail with a New Message).
174 // 2 If the given URI reference does not match a supported scheme
175 // (e.g., "foo:bar"):
176 // 2.1 If it matches an existing pathname (relative to CWD) pointing to
177 // an executable: Results in execution of that executable.
178 // 2.2 If it matches an existing pathname (relative to CWD) pointing to
179 // a non-executable regular file: Results in opening it in TextEdit.
180 // 2.3 If it matches an existing pathname (relative to CWD) pointing to
181 // a directory: Results in opening it in Finder.
182 // 2.4 If it does not match an exitsting pathname (relative to CWD):
183 // Results in "The file /.../foo:bar does not exits." (where "/..." is
184 // the CWD) on stderr and SystemShellExecuteException.
185 aBuffer.append("open");
186 if (dir) {
187 aBuffer.append(" -R");
188 }
189 aBuffer.append(" --");
190#else
191 // Just use xdg-open on non-Mac
192 aBuffer.append("xdg-open");
193#endif
194 aBuffer.append(" ");
195 escapeForShell(aBuffer, OUStringToOString(aURL, osl_getThreadTextEncoding()));
196
197 if ( pDesktopLaunch && *pDesktopLaunch )
198 {
199 aLaunchBuffer.append( pDesktopLaunch + OString::Concat(" "));
200 escapeForShell(aLaunchBuffer, OUStringToOString(aURL, osl_getThreadTextEncoding()));
201 }
202 } else if ((nFlags & css::system::SystemShellExecuteFlags::URIS_ONLY) != 0)
203 {
204 throw css::lang::IllegalArgumentException(
205 "XSystemShellExecute.execute URIS_ONLY with non-absolute"
206 " URI reference "
207 + aCommand,
208 getXWeak(), 0);
209 } else {
210 escapeForShell(aBuffer, OUStringToOString(aCommand, osl_getThreadTextEncoding()));
211 aBuffer.append(" ");
212 if( nFlags != 42 )
213 escapeForShell(aBuffer, OUStringToOString(aParameter, osl_getThreadTextEncoding()));
214 else
215 aBuffer.append(OUStringToOString(aParameter, osl_getThreadTextEncoding()));
216 }
217
218 // Prefer DESKTOP_LAUNCH when available
219 if ( !aLaunchBuffer.isEmpty() )
220 {
221 FILE *pLaunch = popen( aLaunchBuffer.makeStringAndClear().getStr(), "w" );
222 if ( pLaunch != nullptr )
223 {
224 if ( 0 == pclose( pLaunch ) )
225 return;
226 }
227 // Failed, do not try DESKTOP_LAUNCH any more
228 pDesktopLaunch = nullptr;
229 }
230
231 OString cmd =
232#ifdef LINUX
233 // avoid blocking (call it in background)
234 "( " + aBuffer + " ) &";
235#else
236 aBuffer.makeStringAndClear();
237#endif
238 FILE *pLaunch = popen(cmd.getStr(), "w");
239 if ( pLaunch != nullptr )
240 {
241 if ( 0 == pclose( pLaunch ) )
242 return;
243 }
244
245 int nerr = errno;
246 throw SystemShellExecuteException(OUString::createFromAscii( strerror( nerr ) ),
247 static_cast < XSystemShellExecute * > (this), nerr );
248#else // EMSCRIPTEN
249 (void)nFlags;
250
251 css::uno::Reference< css::uri::XUriReference > uri(
252 css::uri::UriReferenceFactory::create(m_xContext)->parse(aCommand));
253 if (!uri.is() || !uri->isAbsolute())
254 throw SystemShellExecuteException("Emscripten can just open absolute URIs.",
255 static_cast<XSystemShellExecute*>(this), 42);
256 if (!aParameter.isEmpty())
257 throw SystemShellExecuteException("Emscripten can't process parameters; encode in URI.",
258 static_cast<XSystemShellExecute*>(this), 42);
259
260 OUString sEscapedURI(rtl::Uri::encode(aCommand, rtl_UriCharClassUric,
261 rtl_UriEncodeIgnoreEscapes, RTL_TEXTENCODING_UTF8));
262 execute_browser(sEscapedURI.toUtf8().getStr());
263#endif
264}
265
266// XServiceInfo
267
269{
270 return "com.sun.star.comp.system.SystemShellExecute";
271}
272
273sal_Bool SAL_CALL ShellExec::supportsService( const OUString& ServiceName )
274{
276}
277
278Sequence< OUString > SAL_CALL ShellExec::getSupportedServiceNames( )
279{
280 return { "com.sun.star.system.SystemShellExecute" };
281}
282
283extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
285 css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
286{
287 return cppu::acquire(new ShellExec(context));
288}
289
290
291/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Reference< XComponentContext > m_xContext
ShellExec(const css::uno::Reference< css::uno::XComponentContext > &xContext)
Definition: shellexec.cxx:75
virtual OUString SAL_CALL getImplementationName() override
Definition: shellexec.cxx:268
virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override
Definition: shellexec.cxx:278
css::uno::Reference< css::uno::XComponentContext > m_xContext
Definition: shellexec.hxx:34
virtual void SAL_CALL execute(const OUString &aCommand, const OUString &aParameter, sal_Int32 nFlags) override
Definition: shellexec.cxx:80
virtual sal_Bool SAL_CALL supportsService(const OUString &ServiceName) override
Definition: shellexec.cxx:273
URL aURL
sal_Int64 n
#define SAL_WARN(area, stream)
#define SAL_INFO(area, stream)
bool CPPUHELPER_DLLPUBLIC supportsService(css::lang::XServiceInfo *implementation, rtl::OUString const &name)
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
bool parse(OUString const &uri, SourceProviderScannerData *data)
SAL_DLLPUBLIC_EXPORT css::uno::XInterface * shell_ShellExec_get_implementation(css::uno::XComponentContext *context, css::uno::Sequence< css::uno::Any > const &)
Definition: shellexec.cxx:284
void execute_browser(const char *sUrl)
Some of our templating stuff clashes with EM_ASM / MAIN_THREAD_EM_ASM:
OUString aCommand
unsigned char sal_Bool
std::unique_ptr< char[]> aBuffer