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 <osl/thread.h>
21 #include <osl/file.hxx>
22 #include <rtl/strbuf.hxx>
23 #include <sal/log.hxx>
25 #include "shellexec.hxx"
26 #include <com/sun/star/system/SystemShellExecuteException.hpp>
27 #include <com/sun/star/system/SystemShellExecuteFlags.hpp>
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>
33 #include <cppuhelper/supportsservice.hxx>
34 #include <comphelper/lok.hxx>
44 #include <rtl/uri.hxx>
45 extern void execute_browser(const char* sUrl
);
48 using com::sun::star::system::XSystemShellExecute
;
49 using com::sun::star::system::SystemShellExecuteException
;
51 using namespace ::com::sun::star::uno
;
52 using namespace ::com::sun::star::lang
;
53 using namespace ::com::sun::star::system::SystemShellExecuteFlags
;
59 void escapeForShell( OStringBuffer
& rBuffer
, const OString
& rURL
)
61 sal_Int32 nmax
= rURL
.getLength();
62 for(sal_Int32 n
=0; n
< nmax
; ++n
)
64 // escape every non alpha numeric characters (excluding a few "known good") by prepending a '\'
66 if( ( c
< 'A' || c
> 'Z' ) && ( c
< 'a' || c
> 'z' ) && ( c
< '0' || c
> '9' ) && c
!= '/' && c
!= '.' )
67 rBuffer
.append( '\\' );
75 ShellExec::ShellExec( const Reference
< XComponentContext
>& xContext
) :
80 void SAL_CALL
ShellExec::execute( const OUString
& aCommand
, const OUString
& aParameter
, sal_Int32 nFlags
)
83 OStringBuffer aBuffer
, aLaunchBuffer
;
85 if (comphelper::LibreOfficeKit::isActive())
87 SAL_WARN("shell", "Unusual - shell attempt to launch " << aCommand
<< " with params " << aParameter
<< " under lok");
91 // DESKTOP_LAUNCH, see http://freedesktop.org/pipermail/xdg/2004-August/004489.html
92 static const char *pDesktopLaunch
= getenv( "DESKTOP_LAUNCH" );
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())
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
103 OUString aURL
= css::uri::ExternalUriReferenceTranslator::create(
104 m_xContext
)->translateToExternal(aCommand
);
105 if ( aURL
.isEmpty() && !aCommand
.isEmpty() )
107 throw RuntimeException(
108 "Cannot translate URI reference to external format: "
115 if (uri
->getScheme().equalsIgnoreAsciiCase("file")) {
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
)),
125 if (!pathname
.convertToString(
126 &pathname8
, RTL_TEXTENCODING_UTF8
,
127 (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR
128 | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR
)))
130 throw css::lang::IllegalArgumentException(
131 "XSystemShellExecute.execute, cannot convert \"" + pathname
+ "\" to UTF-8", {},
135 auto const e2
= lstat(pathname8
.getStr(), &st
);
137 auto const e3
= errno
;
138 SAL_INFO("shell", "lstat(" << pathname8
<< ") failed with errno " << e3
);
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
)) {
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))
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"))
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.,
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 existing 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 existing 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");
187 aBuffer
.append(" -R");
189 aBuffer
.append(" --");
191 // Just use xdg-open on non-Mac
192 aBuffer
.append("xdg-open");
195 escapeForShell(aBuffer
, OUStringToOString(aURL
, osl_getThreadTextEncoding()));
197 if ( pDesktopLaunch
&& *pDesktopLaunch
)
199 aLaunchBuffer
.append( pDesktopLaunch
+ OString::Concat(" "));
200 escapeForShell(aLaunchBuffer
, OUStringToOString(aURL
, osl_getThreadTextEncoding()));
202 } else if ((nFlags
& css::system::SystemShellExecuteFlags::URIS_ONLY
) != 0)
204 throw css::lang::IllegalArgumentException(
205 "XSystemShellExecute.execute URIS_ONLY with non-absolute"
211 auto usingOpen
= false;
212 if (OString pathname8
;
213 aCommand
.convertToString(
214 &pathname8
, RTL_TEXTENCODING_UTF8
,
215 RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR
| RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR
))
217 if (struct stat st
; stat(pathname8
.getStr(), &st
) == 0 && S_ISDIR(st
.st_mode
)) {
219 aBuffer
.append("open -a ");
223 escapeForShell(aBuffer
, OUStringToOString(aCommand
, osl_getThreadTextEncoding()));
224 if (!aParameter
.isEmpty()) {
228 aBuffer
.append("--args ");
232 escapeForShell(aBuffer
, OUStringToOString(aParameter
, osl_getThreadTextEncoding()));
234 aBuffer
.append(OUStringToOString(aParameter
, osl_getThreadTextEncoding()));
238 // Prefer DESKTOP_LAUNCH when available
239 if ( !aLaunchBuffer
.isEmpty() )
241 FILE *pLaunch
= popen( aLaunchBuffer
.makeStringAndClear().getStr(), "w" );
242 if ( pLaunch
!= nullptr )
244 if ( 0 == pclose( pLaunch
) )
247 // Failed, do not try DESKTOP_LAUNCH any more
248 pDesktopLaunch
= nullptr;
253 // avoid blocking (call it in background)
254 "( " + aBuffer
+ " ) &";
256 aBuffer
.makeStringAndClear();
258 FILE *pLaunch
= popen(cmd
.getStr(), "w");
259 if ( pLaunch
!= nullptr )
261 if ( 0 == pclose( pLaunch
) )
266 throw SystemShellExecuteException(OUString::createFromAscii( strerror( nerr
) ),
267 static_cast < XSystemShellExecute
* > (this), nerr
);
271 css::uno::Reference
< css::uri::XUriReference
> uri(
272 css::uri::UriReferenceFactory::create(m_xContext
)->parse(aCommand
));
273 if (!uri
.is() || !uri
->isAbsolute())
274 throw SystemShellExecuteException("Emscripten can just open absolute URIs.",
275 static_cast<XSystemShellExecute
*>(this), 42);
276 if (!aParameter
.isEmpty())
277 throw SystemShellExecuteException("Emscripten can't process parameters; encode in URI.",
278 static_cast<XSystemShellExecute
*>(this), 42);
280 OUString
sEscapedURI(rtl::Uri::encode(aCommand
, rtl_UriCharClassUric
,
281 rtl_UriEncodeIgnoreEscapes
, RTL_TEXTENCODING_UTF8
));
282 execute_browser(sEscapedURI
.toUtf8().getStr());
288 OUString SAL_CALL
ShellExec::getImplementationName( )
290 return u
"com.sun.star.comp.system.SystemShellExecute"_ustr
;
293 sal_Bool SAL_CALL
ShellExec::supportsService( const OUString
& ServiceName
)
295 return cppu::supportsService(this, ServiceName
);
298 Sequence
< OUString
> SAL_CALL
ShellExec::getSupportedServiceNames( )
300 return { u
"com.sun.star.system.SystemShellExecute"_ustr
};
303 extern "C" SAL_DLLPUBLIC_EXPORT
css::uno::XInterface
*
304 shell_ShellExec_get_implementation(
305 css::uno::XComponentContext
* context
, css::uno::Sequence
<css::uno::Any
> const&)
307 return cppu::acquire(new ShellExec(context
));
311 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */