1
0
Fork 0
libreoffice/shell/source/win32/SysShExec.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

422 lines
18 KiB
C++

/* -*- 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 <algorithm>
#include <cassert>
#include <cstddef>
#include <string_view>
#include <osl/diagnose.h>
#include <osl/process.h>
#include <sal/log.hxx>
#include "SysShExec.hxx"
#include <osl/file.hxx>
#include <sal/macros.h>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <com/sun/star/security/AccessControlException.hpp>
#include <com/sun/star/system/SystemShellExecuteException.hpp>
#include <com/sun/star/system/SystemShellExecuteFlags.hpp>
#include <com/sun/star/uri/UriReferenceFactory.hpp>
#include <cppuhelper/supportsservice.hxx>
#include <o3tl/char16_t2wchar_t.hxx>
#include <o3tl/runtimetooustring.hxx>
#include <o3tl/safeCoInitUninit.hxx>
#include <o3tl/string_view.hxx>
#include <tools/urlobj.hxx>
#include <prewin.h>
#include <Shlobj.h>
#include <systools/win32/comtools.hxx>
#include <postwin.h>
using namespace ::com::sun::star::system::SystemShellExecuteFlags;
namespace
{
/* This is the error table that defines the mapping between OS error
codes and errno values */
struct errentry {
unsigned long oscode; /* OS return value */
int errnocode; /* System V error code */
};
struct errentry errtable[] = {
{ ERROR_SUCCESS, osl_File_E_None }, /* 0 */
{ ERROR_INVALID_FUNCTION, osl_File_E_INVAL }, /* 1 */
{ ERROR_FILE_NOT_FOUND, osl_File_E_NOENT }, /* 2 */
{ ERROR_PATH_NOT_FOUND, osl_File_E_NOENT }, /* 3 */
{ ERROR_TOO_MANY_OPEN_FILES, osl_File_E_MFILE }, /* 4 */
{ ERROR_ACCESS_DENIED, osl_File_E_ACCES }, /* 5 */
{ ERROR_INVALID_HANDLE, osl_File_E_BADF }, /* 6 */
{ ERROR_ARENA_TRASHED, osl_File_E_NOMEM }, /* 7 */
{ ERROR_NOT_ENOUGH_MEMORY, osl_File_E_NOMEM }, /* 8 */
{ ERROR_INVALID_BLOCK, osl_File_E_NOMEM }, /* 9 */
{ ERROR_BAD_ENVIRONMENT, osl_File_E_2BIG }, /* 10 */
{ ERROR_BAD_FORMAT, osl_File_E_NOEXEC }, /* 11 */
{ ERROR_INVALID_ACCESS, osl_File_E_INVAL }, /* 12 */
{ ERROR_INVALID_DATA, osl_File_E_INVAL }, /* 13 */
{ ERROR_INVALID_DRIVE, osl_File_E_NOENT }, /* 15 */
{ ERROR_CURRENT_DIRECTORY, osl_File_E_ACCES }, /* 16 */
{ ERROR_NOT_SAME_DEVICE, osl_File_E_XDEV }, /* 17 */
{ ERROR_NO_MORE_FILES, osl_File_E_NOENT }, /* 18 */
{ ERROR_LOCK_VIOLATION, osl_File_E_ACCES }, /* 33 */
{ ERROR_BAD_NETPATH, osl_File_E_NOENT }, /* 53 */
{ ERROR_NETWORK_ACCESS_DENIED, osl_File_E_ACCES }, /* 65 */
{ ERROR_BAD_NET_NAME, osl_File_E_NOENT }, /* 67 */
{ ERROR_FILE_EXISTS, osl_File_E_EXIST }, /* 80 */
{ ERROR_CANNOT_MAKE, osl_File_E_ACCES }, /* 82 */
{ ERROR_FAIL_I24, osl_File_E_ACCES }, /* 83 */
{ ERROR_INVALID_PARAMETER, osl_File_E_INVAL }, /* 87 */
{ ERROR_NO_PROC_SLOTS, osl_File_E_AGAIN }, /* 89 */
{ ERROR_DRIVE_LOCKED, osl_File_E_ACCES }, /* 108 */
{ ERROR_BROKEN_PIPE, osl_File_E_PIPE }, /* 109 */
{ ERROR_DISK_FULL, osl_File_E_NOSPC }, /* 112 */
{ ERROR_INVALID_TARGET_HANDLE, osl_File_E_BADF }, /* 114 */
{ ERROR_INVALID_HANDLE, osl_File_E_INVAL }, /* 124 */
{ ERROR_WAIT_NO_CHILDREN, osl_File_E_CHILD }, /* 128 */
{ ERROR_CHILD_NOT_COMPLETE, osl_File_E_CHILD }, /* 129 */
{ ERROR_DIRECT_ACCESS_HANDLE, osl_File_E_BADF }, /* 130 */
{ ERROR_NEGATIVE_SEEK, osl_File_E_INVAL }, /* 131 */
{ ERROR_SEEK_ON_DEVICE, osl_File_E_ACCES }, /* 132 */
{ ERROR_DIR_NOT_EMPTY, osl_File_E_NOTEMPTY }, /* 145 */
{ ERROR_NOT_LOCKED, osl_File_E_ACCES }, /* 158 */
{ ERROR_BAD_PATHNAME, osl_File_E_NOENT }, /* 161 */
{ ERROR_MAX_THRDS_REACHED, osl_File_E_AGAIN }, /* 164 */
{ ERROR_LOCK_FAILED, osl_File_E_ACCES }, /* 167 */
{ ERROR_ALREADY_EXISTS, osl_File_E_EXIST }, /* 183 */
{ ERROR_FILENAME_EXCED_RANGE, osl_File_E_NOENT }, /* 206 */
{ ERROR_NESTING_NOT_ALLOWED, osl_File_E_AGAIN }, /* 215 */
{ ERROR_NOT_ENOUGH_QUOTA, osl_File_E_NOMEM } /* 1816 */
};
/* size of the table */
#define ERRTABLESIZE (SAL_N_ELEMENTS(errtable))
/* The following two constants must be the minimum and maximum
values in the (contiguous) range of osl_File_E_xec Failure errors. */
#define MIN_EXEC_ERROR ERROR_INVALID_STARTING_CODESEG
#define MAX_EXEC_ERROR ERROR_INFLOOP_IN_RELOC_CHAIN
/* These are the low and high value in the range of errors that are
access violations */
#define MIN_EACCES_RANGE ERROR_WRITE_PROTECT
#define MAX_EACCES_RANGE ERROR_SHARING_BUFFER_EXCEEDED
/*******************************************************************************/
oslFileError _mapError( DWORD dwError )
{
unsigned i;
/* check the table for the OS error code */
for ( i = 0; i < ERRTABLESIZE; ++i )
{
if ( dwError == errtable[i].oscode )
return static_cast<oslFileError>(errtable[i].errnocode);
}
/* The error code wasn't in the table. We check for a range of */
/* osl_File_E_ACCES errors or exec failure errors (ENOEXEC). Otherwise */
/* osl_File_E_INVAL is returned. */
if ( dwError >= MIN_EACCES_RANGE && dwError <= MAX_EACCES_RANGE)
return osl_File_E_ACCES;
else if ( dwError >= MIN_EXEC_ERROR && dwError <= MAX_EXEC_ERROR)
return osl_File_E_NOEXEC;
else
return osl_File_E_INVAL;
}
#define MapError( oserror ) _mapError( oserror )
#define E_UNKNOWN_EXEC_ERROR -1
}
CSysShExec::CSysShExec( const css::uno::Reference< css::uno::XComponentContext >& xContext ) :
WeakComponentImplHelper< css::system::XSystemShellExecute, css::lang::XServiceInfo >( m_aMutex ),
m_xContext(xContext),
mnNbCallCoInitializeExForReinit(0)
{
/*
* As this service is declared thread-affine, it is ensured to be called from a
* dedicated thread, so initialize COM here.
*
* We need COM to be initialized for STA, but osl thread get initialized for MTA.
* Once this changed, we can remove the uninitialize call.
*/
o3tl::safeCoInitializeEx(COINIT_APARTMENTTHREADED, mnNbCallCoInitializeExForReinit);
}
CSysShExec::~CSysShExec()
{
o3tl::safeCoUninitializeReinit(COINIT_MULTITHREADED, mnNbCallCoInitializeExForReinit);
}
namespace
{
bool checkExtension(std::u16string_view extension, std::u16string_view denylist) {
assert(!extension.empty());
for (std::size_t i = 0; i != std::u16string_view::npos;) {
std::u16string_view tok = o3tl::getToken(denylist, ';', i);
o3tl::starts_with(tok, u'.', &tok);
if (o3tl::equalsIgnoreAsciiCase(extension, tok)) {
return false;
}
}
return true;
}
// This callback checks if the found window is the specified process's top-level window,
// and activates the first found such window.
BOOL CALLBACK FindAndActivateProcWnd(HWND hwnd, LPARAM lParam)
{
if (!IsWindowVisible(hwnd))
return TRUE; // continue enumeration
if (GetWindow(hwnd, GW_OWNER)) // not a top-level window
return TRUE; // continue enumeration
const DWORD nParamProcId = static_cast<DWORD>(lParam);
assert(nParamProcId != 0);
DWORD nWndProcId = 0;
(void)GetWindowThreadProcessId(hwnd, &nWndProcId);
if (nWndProcId != nParamProcId)
return TRUE; // continue enumeration
// Found it! Bring it to front
if (IsIconic(hwnd))
{
ShowWindow(hwnd, SW_RESTORE);
}
SetForegroundWindow(hwnd);
SetActiveWindow(hwnd);
return FALSE; // stop enumeration
}
}
void SAL_CALL CSysShExec::execute( const OUString& aCommand, const OUString& aParameter, sal_Int32 nFlags )
{
// parameter checking
if (0 == aCommand.getLength())
throw css::lang::IllegalArgumentException(
"Empty command",
static_cast< css::system::XSystemShellExecute* >( this ),
1 );
if ((nFlags & ~(NO_SYSTEM_ERROR_MESSAGE | URIS_ONLY)) != 0)
throw css::lang::IllegalArgumentException(
"Invalid Flags specified",
static_cast< css::system::XSystemShellExecute* >( this ),
3 );
OUString preprocessed_command(aCommand);
if ((nFlags & URIS_ONLY) != 0)
{
css::uno::Reference< css::uri::XUriReference > uri(
css::uri::UriReferenceFactory::create(m_xContext)->parse(aCommand));
if (!(uri.is() && uri->isAbsolute()))
{
throw css::lang::IllegalArgumentException(
"XSystemShellExecute.execute URIS_ONLY with"
" non-absolute URI reference "
+ aCommand,
getXWeak(), 0);
}
if (uri->getScheme().equalsIgnoreAsciiCase("file")) {
// ShellExecuteExW appears to ignore the fragment of a file URL anyway, so remove it:
uri->clearFragment();
OUString pathname;
auto const e1
= osl::FileBase::getSystemPathFromFileURL(uri->getUriReference(), pathname);
if (e1 != osl::FileBase::E_None) {
throw css::lang::IllegalArgumentException(
("XSystemShellExecute.execute, getSystemPathFromFileURL <" + aCommand
+ "> failed with " + OUString::number(e1)),
{}, 0);
}
const int MAX_LONG_PATH = 32767; // max longpath on WinNT
if (pathname.getLength() >= MAX_LONG_PATH)
{
throw css::lang::IllegalArgumentException(
"XSystemShellExecute.execute, path <" + pathname + "> too long", {}, 0);
}
wchar_t path[MAX_LONG_PATH];
wcscpy_s(path, o3tl::toW(pathname.getStr()));
for (int i = 0;; ++i) {
// tdf#130216: normalize c:\path\to\something\..\else into c:\path\to\else
if (PathResolve(path, nullptr, PRF_VERIFYEXISTS | PRF_REQUIREABSOLUTE) == 0)
{
throw css::lang::IllegalArgumentException(
OUString::Concat(u"XSystemShellExecute.execute, PathResolve(") + o3tl::toU(path)
+ ") failed",
{}, 0);
}
if (SHGetFileInfoW(path, 0, nullptr, 0, SHGFI_EXETYPE) != 0)
{
throw css::security::AccessControlException(
"XSystemShellExecute.execute, cannot process <" + aCommand + ">", {}, {});
}
SHFILEINFOW info;
if (SHGetFileInfoW(path, 0, &info, sizeof info, SHGFI_ATTRIBUTES) == 0)
{
throw css::lang::IllegalArgumentException(
OUString::Concat(u"XSystemShellExecute.execute, SHGetFileInfoW(") + o3tl::toU(path) + ") failed", {},
0);
}
if ((info.dwAttributes & SFGAO_LINK) == 0) {
break;
}
try
{
sal::systools::COMReference<IShellLinkW> link(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER);
sal::systools::COMReference<IPersistFile> file(link, sal::systools::COM_QUERY_THROW);
sal::systools::ThrowIfFailed(file->Load(path, STGM_READ),
"IPersistFile.Load failed");
sal::systools::ThrowIfFailed(link->Resolve(nullptr, SLR_UPDATE | SLR_NO_UI),
"IShellLink.Resolve failed");
sal::systools::ThrowIfFailed(link->GetPath(path, std::size(path), nullptr, SLGP_RAWPATH),
"IShellLink.GetPath failed");
}
catch (sal::systools::ComError& e)
{
throw css::lang::IllegalArgumentException(
("XSystemShellExecute.execute, " + o3tl::runtimeToOUString(e.what())
+ " at " + o3tl::runtimeToOUString(e.GetLocation().file_name()) + ":"
+ OUString::number(e.GetLocation().line()) + " error "
+ OUString::number(e.GetHresult())),
{}, 0);
}
// Fail at some arbitrary nesting depth, to avoid an infinite loop:
if (i == 30) {
throw css::lang::IllegalArgumentException(
"XSystemShellExecute.execute, link depth exceeded for <" + aCommand + ">",
{}, 0);
}
}
std::u16string_view resulting_path(o3tl::toU(path));
// ShellExecuteExW appears to ignore trailing dots, so remove them:
while (o3tl::ends_with(resulting_path, u".", &resulting_path)) {}
auto const n = resulting_path.find_last_of('.');
if (n != std::u16string_view::npos && n > resulting_path.find_last_of('\\')) {
auto const ext = resulting_path.substr(n + 1);
if (!ext.empty()) {
OUString env;
if (osl_getEnvironment(u"PATHEXT"_ustr.pData, &env.pData)
!= osl_Process_E_None)
{
SAL_INFO("shell", "osl_getEnvironment(PATHEXT) failed");
}
if (!(checkExtension(ext, env)
&& checkExtension(
ext,
u".ADE;.ADP;.APK;.APPLICATION;.APPX;.APPXBUNDLE;.BAT;.CAB;.CHM;.CLASS;"
".CMD;.COM;.CPL;.DLL;.DMG;.EX;.EX_;.EXE;.GADGET;.HTA;.INF;.INS;.IPA;"
".ISO;.ISP;.JAR;.JS;.JSE;.LIB;.LNK;.MDE;.MSC;.MSH;.MSH1;.MSH2;.MSHXML;"
".MSH1XML;.MSH2XML;.MSI;.MSIX;.MSIXBUNDLE;.MSP;.MST;.NSH;.PIF;.PS1;"
".PS1XML;.PS2;.PS2XML;.PSC1;.PSC2;.PY;.REG;.SCF;.SCR;.SCT;.SHB;.SYS;"
".VB;.VBE;.VBS;.VXD;.WS;.WSC;.WSF;.WSH;")))
{
throw css::security::AccessControlException(
"XSystemShellExecute.execute, cannot process <" + aCommand + ">", {},
{});
}
}
}
preprocessed_command = resulting_path;
} else {
// Filter out input that technically is a non-file URI, but could be interpreted by
// ShellExecuteExW as a file system pathname.
if (INetURLObject(aCommand, INetProtocol::File).GetProtocol() == INetProtocol::File) {
throw css::lang::IllegalArgumentException(
"XSystemShellExecute.execute URIS_ONLY with non-URI pathname " + aCommand,
getXWeak(), 0);
}
}
}
SHELLEXECUTEINFOW sei;
ZeroMemory(&sei, sizeof( sei));
sei.cbSize = sizeof(sei);
sei.lpFile = o3tl::toW(preprocessed_command.getStr());
sei.lpParameters = o3tl::toW(aParameter.getStr());
sei.nShow = SW_SHOWNORMAL;
sei.fMask = SEE_MASK_NOCLOSEPROCESS; // we need sei.hProcess
if (NO_SYSTEM_ERROR_MESSAGE & nFlags)
sei.fMask |= SEE_MASK_FLAG_NO_UI;
SetLastError( 0 );
bool bRet = ShellExecuteExW(&sei);
if (!bRet && (nFlags & NO_SYSTEM_ERROR_MESSAGE))
{
// ShellExecuteEx fails to set an error code
// we return osl_File_E_INVAL
sal_Int32 psxErr = GetLastError();
if (ERROR_SUCCESS == psxErr)
psxErr = E_UNKNOWN_EXEC_ERROR;
else
psxErr = MapError(psxErr);
throw css::system::SystemShellExecuteException(
"Error executing command",
static_cast< css::system::XSystemShellExecute* >(this),
psxErr);
}
else
{
// Get Permission make changes to the Window of the created Process
const DWORD procId = GetProcessId(sei.hProcess);
if (procId != 0)
{
AllowSetForegroundWindow(procId);
WaitForInputIdle(sei.hProcess, 1000); // so that main window is created; imperfect
EnumWindows(FindAndActivateProcWnd, static_cast<LPARAM>(procId));
}
}
// Close the handle for the created childprocess when we are done
CloseHandle(sei.hProcess);
}
// XServiceInfo
OUString SAL_CALL CSysShExec::getImplementationName( )
{
return "com.sun.star.sys.shell.SystemShellExecute";
}
sal_Bool SAL_CALL CSysShExec::supportsService( const OUString& ServiceName )
{
return cppu::supportsService(this, ServiceName);
}
css::uno::Sequence< OUString > SAL_CALL CSysShExec::getSupportedServiceNames( )
{
return { "com.sun.star.system.SystemShellExecute" };
}
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
shell_CSysShExec_get_implementation(
css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
{
return cppu::acquire(new CSysShExec(context));
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */