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 .
23 #include <string_view>
25 #include <osl/diagnose.h>
26 #include <osl/process.h>
27 #include <sal/log.hxx>
28 #include "SysShExec.hxx"
29 #include <osl/file.hxx>
30 #include <sal/macros.h>
31 #include <com/sun/star/lang/IllegalArgumentException.hpp>
32 #include <com/sun/star/security/AccessControlException.hpp>
33 #include <com/sun/star/system/SystemShellExecuteException.hpp>
34 #include <com/sun/star/system/SystemShellExecuteFlags.hpp>
35 #include <com/sun/star/uri/UriReferenceFactory.hpp>
36 #include <cppuhelper/supportsservice.hxx>
37 #include <o3tl/char16_t2wchar_t.hxx>
38 #include <o3tl/runtimetooustring.hxx>
39 #include <o3tl/safeCoInitUninit.hxx>
40 #include <o3tl/string_view.hxx>
44 #include <systools/win32/comtools.hxx>
47 using namespace ::com::sun::star::system::SystemShellExecuteFlags
;
51 /* This is the error table that defines the mapping between OS error
52 codes and errno values */
55 unsigned long oscode
; /* OS return value */
56 int errnocode
; /* System V error code */
59 struct errentry errtable
[] = {
60 { ERROR_SUCCESS
, osl_File_E_None
}, /* 0 */
61 { ERROR_INVALID_FUNCTION
, osl_File_E_INVAL
}, /* 1 */
62 { ERROR_FILE_NOT_FOUND
, osl_File_E_NOENT
}, /* 2 */
63 { ERROR_PATH_NOT_FOUND
, osl_File_E_NOENT
}, /* 3 */
64 { ERROR_TOO_MANY_OPEN_FILES
, osl_File_E_MFILE
}, /* 4 */
65 { ERROR_ACCESS_DENIED
, osl_File_E_ACCES
}, /* 5 */
66 { ERROR_INVALID_HANDLE
, osl_File_E_BADF
}, /* 6 */
67 { ERROR_ARENA_TRASHED
, osl_File_E_NOMEM
}, /* 7 */
68 { ERROR_NOT_ENOUGH_MEMORY
, osl_File_E_NOMEM
}, /* 8 */
69 { ERROR_INVALID_BLOCK
, osl_File_E_NOMEM
}, /* 9 */
70 { ERROR_BAD_ENVIRONMENT
, osl_File_E_2BIG
}, /* 10 */
71 { ERROR_BAD_FORMAT
, osl_File_E_NOEXEC
}, /* 11 */
72 { ERROR_INVALID_ACCESS
, osl_File_E_INVAL
}, /* 12 */
73 { ERROR_INVALID_DATA
, osl_File_E_INVAL
}, /* 13 */
74 { ERROR_INVALID_DRIVE
, osl_File_E_NOENT
}, /* 15 */
75 { ERROR_CURRENT_DIRECTORY
, osl_File_E_ACCES
}, /* 16 */
76 { ERROR_NOT_SAME_DEVICE
, osl_File_E_XDEV
}, /* 17 */
77 { ERROR_NO_MORE_FILES
, osl_File_E_NOENT
}, /* 18 */
78 { ERROR_LOCK_VIOLATION
, osl_File_E_ACCES
}, /* 33 */
79 { ERROR_BAD_NETPATH
, osl_File_E_NOENT
}, /* 53 */
80 { ERROR_NETWORK_ACCESS_DENIED
, osl_File_E_ACCES
}, /* 65 */
81 { ERROR_BAD_NET_NAME
, osl_File_E_NOENT
}, /* 67 */
82 { ERROR_FILE_EXISTS
, osl_File_E_EXIST
}, /* 80 */
83 { ERROR_CANNOT_MAKE
, osl_File_E_ACCES
}, /* 82 */
84 { ERROR_FAIL_I24
, osl_File_E_ACCES
}, /* 83 */
85 { ERROR_INVALID_PARAMETER
, osl_File_E_INVAL
}, /* 87 */
86 { ERROR_NO_PROC_SLOTS
, osl_File_E_AGAIN
}, /* 89 */
87 { ERROR_DRIVE_LOCKED
, osl_File_E_ACCES
}, /* 108 */
88 { ERROR_BROKEN_PIPE
, osl_File_E_PIPE
}, /* 109 */
89 { ERROR_DISK_FULL
, osl_File_E_NOSPC
}, /* 112 */
90 { ERROR_INVALID_TARGET_HANDLE
, osl_File_E_BADF
}, /* 114 */
91 { ERROR_INVALID_HANDLE
, osl_File_E_INVAL
}, /* 124 */
92 { ERROR_WAIT_NO_CHILDREN
, osl_File_E_CHILD
}, /* 128 */
93 { ERROR_CHILD_NOT_COMPLETE
, osl_File_E_CHILD
}, /* 129 */
94 { ERROR_DIRECT_ACCESS_HANDLE
, osl_File_E_BADF
}, /* 130 */
95 { ERROR_NEGATIVE_SEEK
, osl_File_E_INVAL
}, /* 131 */
96 { ERROR_SEEK_ON_DEVICE
, osl_File_E_ACCES
}, /* 132 */
97 { ERROR_DIR_NOT_EMPTY
, osl_File_E_NOTEMPTY
}, /* 145 */
98 { ERROR_NOT_LOCKED
, osl_File_E_ACCES
}, /* 158 */
99 { ERROR_BAD_PATHNAME
, osl_File_E_NOENT
}, /* 161 */
100 { ERROR_MAX_THRDS_REACHED
, osl_File_E_AGAIN
}, /* 164 */
101 { ERROR_LOCK_FAILED
, osl_File_E_ACCES
}, /* 167 */
102 { ERROR_ALREADY_EXISTS
, osl_File_E_EXIST
}, /* 183 */
103 { ERROR_FILENAME_EXCED_RANGE
, osl_File_E_NOENT
}, /* 206 */
104 { ERROR_NESTING_NOT_ALLOWED
, osl_File_E_AGAIN
}, /* 215 */
105 { ERROR_NOT_ENOUGH_QUOTA
, osl_File_E_NOMEM
} /* 1816 */
108 /* size of the table */
109 #define ERRTABLESIZE (SAL_N_ELEMENTS(errtable))
111 /* The following two constants must be the minimum and maximum
112 values in the (contiguous) range of osl_File_E_xec Failure errors. */
113 #define MIN_EXEC_ERROR ERROR_INVALID_STARTING_CODESEG
114 #define MAX_EXEC_ERROR ERROR_INFLOOP_IN_RELOC_CHAIN
116 /* These are the low and high value in the range of errors that are
118 #define MIN_EACCES_RANGE ERROR_WRITE_PROTECT
119 #define MAX_EACCES_RANGE ERROR_SHARING_BUFFER_EXCEEDED
122 /*******************************************************************************/
124 oslFileError
_mapError( DWORD dwError
)
128 /* check the table for the OS error code */
129 for ( i
= 0; i
< ERRTABLESIZE
; ++i
)
131 if ( dwError
== errtable
[i
].oscode
)
132 return static_cast<oslFileError
>(errtable
[i
].errnocode
);
135 /* The error code wasn't in the table. We check for a range of */
136 /* osl_File_E_ACCES errors or exec failure errors (ENOEXEC). Otherwise */
137 /* osl_File_E_INVAL is returned. */
139 if ( dwError
>= MIN_EACCES_RANGE
&& dwError
<= MAX_EACCES_RANGE
)
140 return osl_File_E_ACCES
;
141 else if ( dwError
>= MIN_EXEC_ERROR
&& dwError
<= MAX_EXEC_ERROR
)
142 return osl_File_E_NOEXEC
;
144 return osl_File_E_INVAL
;
147 #define MapError( oserror ) _mapError( oserror )
149 #define E_UNKNOWN_EXEC_ERROR -1
152 CSysShExec::CSysShExec( const css::uno::Reference
< css::uno::XComponentContext
>& xContext
) :
153 WeakComponentImplHelper
< css::system::XSystemShellExecute
, css::lang::XServiceInfo
>( m_aMutex
),
154 m_xContext(xContext
),
155 mnNbCallCoInitializeExForReinit(0)
158 * As this service is declared thread-affine, it is ensured to be called from a
159 * dedicated thread, so initialize COM here.
161 * We need COM to be initialized for STA, but osl thread get initialized for MTA.
162 * Once this changed, we can remove the uninitialize call.
164 o3tl::safeCoInitializeEx(COINIT_APARTMENTTHREADED
, mnNbCallCoInitializeExForReinit
);
166 CSysShExec::~CSysShExec()
168 o3tl::safeCoUninitializeReinit(COINIT_MULTITHREADED
, mnNbCallCoInitializeExForReinit
);
173 bool checkExtension(std::u16string_view extension
, std::u16string_view denylist
) {
174 assert(!extension
.empty());
175 for (std::size_t i
= 0; i
!= std::u16string_view::npos
;) {
176 std::u16string_view tok
= o3tl::getToken(denylist
, ';', i
);
177 o3tl::starts_with(tok
, u
'.', &tok
);
178 if (o3tl::equalsIgnoreAsciiCase(extension
, tok
)) {
185 // This callback checks if the found window is the specified process's top-level window,
186 // and activates the first found such window.
187 BOOL CALLBACK
FindAndActivateProcWnd(HWND hwnd
, LPARAM lParam
)
189 if (!IsWindowVisible(hwnd
))
190 return TRUE
; // continue enumeration
191 if (GetWindow(hwnd
, GW_OWNER
)) // not a top-level window
192 return TRUE
; // continue enumeration
193 const DWORD nParamProcId
= static_cast<DWORD
>(lParam
);
194 assert(nParamProcId
!= 0);
195 DWORD nWndProcId
= 0;
196 (void)GetWindowThreadProcessId(hwnd
, &nWndProcId
);
197 if (nWndProcId
!= nParamProcId
)
198 return TRUE
; // continue enumeration
200 // Found it! Bring it to front
203 ShowWindow(hwnd
, SW_RESTORE
);
205 SetForegroundWindow(hwnd
);
206 SetActiveWindow(hwnd
);
207 return FALSE
; // stop enumeration
211 void SAL_CALL
CSysShExec::execute( const OUString
& aCommand
, const OUString
& aParameter
, sal_Int32 nFlags
)
213 // parameter checking
214 if (0 == aCommand
.getLength())
215 throw css::lang::IllegalArgumentException(
217 static_cast< css::system::XSystemShellExecute
* >( this ),
220 if ((nFlags
& ~(NO_SYSTEM_ERROR_MESSAGE
| URIS_ONLY
)) != 0)
221 throw css::lang::IllegalArgumentException(
222 "Invalid Flags specified",
223 static_cast< css::system::XSystemShellExecute
* >( this ),
226 OUString
preprocessed_command(aCommand
);
227 if ((nFlags
& URIS_ONLY
) != 0)
229 css::uno::Reference
< css::uri::XUriReference
> uri(
230 css::uri::UriReferenceFactory::create(m_xContext
)->parse(aCommand
));
231 if (!(uri
.is() && uri
->isAbsolute()))
233 throw css::lang::IllegalArgumentException(
234 "XSystemShellExecute.execute URIS_ONLY with"
235 " non-absolute URI reference "
239 if (uri
->getScheme().equalsIgnoreAsciiCase("file")) {
240 // ShellExecuteExW appears to ignore the fragment of a file URL anyway, so remove it:
241 uri
->clearFragment();
244 = osl::FileBase::getSystemPathFromFileURL(uri
->getUriReference(), pathname
);
245 if (e1
!= osl::FileBase::E_None
) {
246 throw css::lang::IllegalArgumentException(
247 ("XSystemShellExecute.execute, getSystemPathFromFileURL <" + aCommand
248 + "> failed with " + OUString::number(e1
)),
251 const int MAX_LONG_PATH
= 32767; // max longpath on WinNT
252 if (pathname
.getLength() >= MAX_LONG_PATH
)
254 throw css::lang::IllegalArgumentException(
255 "XSystemShellExecute.execute, path <" + pathname
+ "> too long", {}, 0);
257 wchar_t path
[MAX_LONG_PATH
];
258 wcscpy_s(path
, o3tl::toW(pathname
.getStr()));
259 for (int i
= 0;; ++i
) {
260 // tdf#130216: normalize c:\path\to\something\..\else into c:\path\to\else
261 if (PathResolve(path
, nullptr, PRF_VERIFYEXISTS
| PRF_REQUIREABSOLUTE
) == 0)
263 throw css::lang::IllegalArgumentException(
264 OUString::Concat(u
"XSystemShellExecute.execute, PathResolve(") + o3tl::toU(path
)
268 if (SHGetFileInfoW(path
, 0, nullptr, 0, SHGFI_EXETYPE
) != 0)
270 throw css::security::AccessControlException(
271 "XSystemShellExecute.execute, cannot process <" + aCommand
+ ">", {}, {});
274 if (SHGetFileInfoW(path
, 0, &info
, sizeof info
, SHGFI_ATTRIBUTES
) == 0)
276 throw css::lang::IllegalArgumentException(
277 OUString::Concat(u
"XSystemShellExecute.execute, SHGetFileInfoW(") + o3tl::toU(path
) + ") failed", {},
280 if ((info
.dwAttributes
& SFGAO_LINK
) == 0) {
285 sal::systools::COMReference
<IShellLinkW
> link(CLSID_ShellLink
, nullptr, CLSCTX_INPROC_SERVER
);
286 sal::systools::COMReference
<IPersistFile
> file(link
, sal::systools::COM_QUERY_THROW
);
287 sal::systools::ThrowIfFailed(file
->Load(path
, STGM_READ
),
288 "IPersistFile.Load failed");
289 sal::systools::ThrowIfFailed(link
->Resolve(nullptr, SLR_UPDATE
| SLR_NO_UI
),
290 "IShellLink.Resolve failed");
291 sal::systools::ThrowIfFailed(link
->GetPath(path
, std::size(path
), nullptr, SLGP_RAWPATH
),
292 "IShellLink.GetPath failed");
294 catch (sal::systools::ComError
& e
)
296 throw css::lang::IllegalArgumentException(
297 ("XSystemShellExecute.execute, " + o3tl::runtimeToOUString(e
.what())
298 + " at " + o3tl::runtimeToOUString(e
.GetLocation().file_name()) + ":"
299 + OUString::number(e
.GetLocation().line()) + " error "
300 + OUString::number(e
.GetHresult())),
303 // Fail at some arbitrary nesting depth, to avoid an infinite loop:
305 throw css::lang::IllegalArgumentException(
306 "XSystemShellExecute.execute, link depth exceeded for <" + aCommand
+ ">",
310 std::u16string_view
resulting_path(o3tl::toU(path
));
311 // ShellExecuteExW appears to ignore trailing dots, so remove them:
312 while (o3tl::ends_with(resulting_path
, u
".", &resulting_path
)) {}
313 auto const n
= resulting_path
.find_last_of('.');
314 if (n
!= std::u16string_view::npos
&& n
> resulting_path
.find_last_of('\\')) {
315 auto const ext
= resulting_path
.substr(n
+ 1);
318 if (osl_getEnvironment(u
"PATHEXT"_ustr
.pData
, &env
.pData
)
319 != osl_Process_E_None
)
321 SAL_INFO("shell", "osl_getEnvironment(PATHEXT) failed");
323 if (!(checkExtension(ext
, env
)
326 u
".ADE;.ADP;.APK;.APPLICATION;.APPX;.APPXBUNDLE;.BAT;.CAB;.CHM;.CLASS;"
327 ".CMD;.COM;.CPL;.DLL;.DMG;.EX;.EX_;.EXE;.GADGET;.HTA;.INF;.INS;.IPA;"
328 ".ISO;.ISP;.JAR;.JS;.JSE;.LIB;.LNK;.MDE;.MSC;.MSH;.MSH1;.MSH2;.MSHXML;"
329 ".MSH1XML;.MSH2XML;.MSI;.MSIX;.MSIXBUNDLE;.MSP;.MST;.NSH;.PIF;.PS1;"
330 ".PS1XML;.PS2;.PS2XML;.PSC1;.PSC2;.PY;.REG;.SCF;.SCR;.SCT;.SHB;.SYS;"
331 ".VB;.VBE;.VBS;.VXD;.WS;.WSC;.WSF;.WSH;")))
333 throw css::security::AccessControlException(
334 "XSystemShellExecute.execute, cannot process <" + aCommand
+ ">", {},
339 preprocessed_command
= resulting_path
;
343 SHELLEXECUTEINFOW sei
;
344 ZeroMemory(&sei
, sizeof( sei
));
346 sei
.cbSize
= sizeof(sei
);
347 sei
.lpFile
= o3tl::toW(preprocessed_command
.getStr());
348 sei
.lpParameters
= o3tl::toW(aParameter
.getStr());
349 sei
.nShow
= SW_SHOWNORMAL
;
350 sei
.fMask
= SEE_MASK_NOCLOSEPROCESS
; // we need sei.hProcess
352 if (NO_SYSTEM_ERROR_MESSAGE
& nFlags
)
353 sei
.fMask
|= SEE_MASK_FLAG_NO_UI
;
357 bool bRet
= ShellExecuteExW(&sei
);
359 if (!bRet
&& (nFlags
& NO_SYSTEM_ERROR_MESSAGE
))
361 // ShellExecuteEx fails to set an error code
362 // we return osl_File_E_INVAL
363 sal_Int32 psxErr
= GetLastError();
364 if (ERROR_SUCCESS
== psxErr
)
365 psxErr
= E_UNKNOWN_EXEC_ERROR
;
367 psxErr
= MapError(psxErr
);
369 throw css::system::SystemShellExecuteException(
370 "Error executing command",
371 static_cast< css::system::XSystemShellExecute
* >(this),
376 // Get Permission make changes to the Window of the created Process
377 const DWORD procId
= GetProcessId(sei
.hProcess
);
380 AllowSetForegroundWindow(procId
);
381 WaitForInputIdle(sei
.hProcess
, 1000); // so that main window is created; imperfect
382 EnumWindows(FindAndActivateProcWnd
, static_cast<LPARAM
>(procId
));
386 // Close the handle for the created childprocess when we are done
387 CloseHandle(sei
.hProcess
);
392 OUString SAL_CALL
CSysShExec::getImplementationName( )
394 return "com.sun.star.sys.shell.SystemShellExecute";
397 sal_Bool SAL_CALL
CSysShExec::supportsService( const OUString
& ServiceName
)
399 return cppu::supportsService(this, ServiceName
);
402 css::uno::Sequence
< OUString
> SAL_CALL
CSysShExec::getSupportedServiceNames( )
404 return { "com.sun.star.system.SystemShellExecute" };
407 extern "C" SAL_DLLPUBLIC_EXPORT
css::uno::XInterface
*
408 shell_CSysShExec_get_implementation(
409 css::uno::XComponentContext
* context
, css::uno::Sequence
<css::uno::Any
> const&)
411 return cppu::acquire(new CSysShExec(context
));
413 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */