diff options
Diffstat (limited to 'src/VBox/HostServices/SharedClipboard')
23 files changed, 10540 insertions, 0 deletions
diff --git a/src/VBox/HostServices/SharedClipboard/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/Makefile.kmk new file mode 100644 index 00000000..e8c9b7b6 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/Makefile.kmk @@ -0,0 +1,121 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Shared Clipboard Host Service. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefile(s). +include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + +# +# The Shared Clipboard service DLL. +# +DLLS += VBoxSharedClipboard +VBoxSharedClipboard_TEMPLATE = VBoxR3Dll +VBoxSharedClipboard_DEFS = \ + VBOX_WITH_HGCM \ + $(if $(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS),VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS,) +VBoxSharedClipboard_INCS.win = \ + $(VBOX_PATH_SDK) + +VBoxSharedClipboard_SOURCES = \ + VBoxSharedClipboardSvc.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp +VBoxSharedClipboard_SOURCES.win = \ + VBoxSharedClipboardSvc-win.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp \ + VBoxSharedClipboardSvc.rc +VBoxSharedClipboard_SOURCES.darwin = \ + VBoxSharedClipboardSvc-darwin.cpp \ + darwin-pasteboard.cpp +ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if1of ($(KBUILD_TARGET), linux) + VBoxSharedClipboard_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + VBoxSharedClipboard_SOURCES += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp + endif + VBoxSharedClipboard_DEFS += VBOX_WITH_SHARED_CLIPBOARD_HOST + VBoxSharedClipboard_SOURCES += \ + VBoxSharedClipboardSvc-transfers.cpp \ + VBoxSharedClipboardSvc-utils.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardPath.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp + VBoxSharedClipboard_SOURCES.win += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp +endif +if1of ($(KBUILD_TARGET), linux solaris freebsd) ## @todo X11 + ifndef VBOX_HEADLESS + VBoxSharedClipboard_SOURCES += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp \ + VBoxSharedClipboardSvc-x11.cpp + else + VBoxSharedClipboard_SOURCES += \ + VBoxSharedClipboardSvc-x11-stubs.cpp + endif +endif + +VBoxSharedClipboard_LIBS = \ + $(LIB_RUNTIME) +if1of ($(KBUILD_TARGET), linux solaris freebsd) + ifndef VBOX_HEADLESS + VBoxSharedClipboard_LIBPATH = \ + $(VBOX_LIBPATH_X11) + VBoxSharedClipboard_LIBS += \ + Xt \ + X11 + endif +endif + +VBoxSharedClipboard_LDFLAGS.darwin = \ + -framework ApplicationServices -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxSharedClipboard.dylib + +if 0 ## Disabled for now; needs to be adapted to the new protocol first. + if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) + if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris) + # + # Set this in LocalConfig.kmk if you are working on the X11 clipboard service + # to automatically run the unit test at build time. + # OTHERS += $(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run + PROGRAMS += tstClipboardX11-2 + TESTING += $(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run + tstClipboardX11-2_TEMPLATE = VBoxR3TstExe + tstClipboardX11-2_DEFS = VBOX_WITH_HGCM TESTCASE + tstClipboardX11-2_SOURCES = VBoxSharedClipboardSvc-x11.cpp + tstClipboardX11-2_LIBS = $(LIB_RUNTIME) + tstClipboardX11-2_CLEAN = $(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run + + $$(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run: $$(tstClipboardX11-2_1_STAGE_TARGET) + export VBOX_LOG_DEST=nofile; $(tstClipboardX11-2_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + endif # 1of ($(KBUILD_TARGET),freebsd linux netbsd openbsd solaris) + endif +endif + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-darwin.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-darwin.cpp new file mode 100644 index 00000000..23e6cd8d --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-darwin.cpp @@ -0,0 +1,346 @@ +/* $Id: VBoxSharedClipboardSvc-darwin.cpp $ */ +/** @file + * Shared Clipboard Service - Mac OS X host. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <VBox/HostServices/VBoxClipboardSvc.h> + +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/process.h> +#include <iprt/rand.h> +#include <iprt/string.h> +#include <iprt/thread.h> + +#include "VBoxSharedClipboardSvc-internal.h" +#include "darwin-pasteboard.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Global clipboard context information */ +typedef struct SHCLCONTEXT +{ + /** We have a separate thread to poll for new clipboard content. */ + RTTHREAD hThread; + /** Termination indicator. */ + bool volatile fTerminate; + /** The reference to the current pasteboard */ + PasteboardRef hPasteboard; + /** Shared clipboard client. */ + PSHCLCLIENT pClient; + /** Random 64-bit number embedded into szGuestOwnershipFlavor. */ + uint64_t idGuestOwnership; + /** Ownership flavor CFStringRef returned by takePasteboardOwnership(). + * This is the same a szGuestOwnershipFlavor only in core foundation terms. */ + void *hStrOwnershipFlavor; + /** The guest ownership flavor (type) string. */ + char szGuestOwnershipFlavor[64]; +} SHCLCONTEXT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Only one client is supported. There seems to be no need for more clients. */ +static SHCLCONTEXT g_ctx; + + +/** + * Checks if something is present on the clipboard and calls shclSvcReportMsg. + * + * @returns IPRT status code (ignored). + * @param pCtx The context. + * + * @note Call must own lock. + */ +static int vboxClipboardChanged(SHCLCONTEXT *pCtx) +{ + if (pCtx->pClient == NULL) + return VINF_SUCCESS; + + /* Retrieve the formats currently in the clipboard and supported by vbox */ + uint32_t fFormats = 0; + bool fChanged = false; + int rc = queryNewPasteboardFormats(pCtx->hPasteboard, pCtx->idGuestOwnership, pCtx->hStrOwnershipFlavor, + &fFormats, &fChanged); + if ( RT_SUCCESS(rc) + && fChanged + && ShClSvcIsBackendActive()) + rc = ShClSvcHostReportFormats(pCtx->pClient, fFormats); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * @callback_method_impl{FNRTTHREAD, The poller thread. + * + * This thread will check for the arrival of new data on the clipboard.} + */ +static DECLCALLBACK(int) vboxClipboardThread(RTTHREAD ThreadSelf, void *pvUser) +{ + SHCLCONTEXT *pCtx = (SHCLCONTEXT *)pvUser; + AssertPtr(pCtx); + LogFlowFuncEnter(); + + while (!pCtx->fTerminate) + { + /* call this behind the lock because we don't know if the api is + thread safe and in any case we're calling several methods. */ + ShClSvcLock(); + vboxClipboardChanged(pCtx); + ShClSvcUnlock(); + + /* Sleep for 200 msecs before next poll */ + RTThreadUserWait(ThreadSelf, 200); + } + + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} + + +int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) +{ + RT_NOREF(pBackend, pTable); + g_ctx.fTerminate = false; + + int rc = initPasteboard(&g_ctx.hPasteboard); + AssertRCReturn(rc, rc); + + rc = RTThreadCreate(&g_ctx.hThread, vboxClipboardThread, &g_ctx, 0, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLIP"); + if (RT_FAILURE(rc)) + { + g_ctx.hThread = NIL_RTTHREAD; + destroyPasteboard(&g_ctx.hPasteboard); + } + + return rc; +} + +void ShClBackendDestroy(PSHCLBACKEND pBackend) +{ + RT_NOREF(pBackend); + + /* + * Signal the termination of the polling thread and wait for it to respond. + */ + ASMAtomicWriteBool(&g_ctx.fTerminate, true); + int rc = RTThreadUserSignal(g_ctx.hThread); + AssertRC(rc); + rc = RTThreadWait(g_ctx.hThread, RT_INDEFINITE_WAIT, NULL); + AssertRC(rc); + + /* + * Destroy the hPasteboard and uninitialize the global context record. + */ + destroyPasteboard(&g_ctx.hPasteboard); + g_ctx.hThread = NIL_RTTHREAD; + g_ctx.pClient = NULL; +} + +int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) +{ + RT_NOREF(pBackend, fHeadless); + + if (g_ctx.pClient != NULL) + { + /* One client only. */ + return VERR_NOT_SUPPORTED; + } + + ShClSvcLock(); + + pClient->State.pCtx = &g_ctx; + pClient->State.pCtx->pClient = pClient; + + ShClSvcUnlock(); + + return VINF_SUCCESS; +} + +int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend); + + /* Sync the host clipboard content with the client. */ + ShClSvcLock(); + + int rc = vboxClipboardChanged(pClient->State.pCtx); + + ShClSvcUnlock(); + + return rc; +} + +int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend); + + ShClSvcLock(); + + pClient->State.pCtx->pClient = NULL; + + ShClSvcUnlock(); + + return VINF_SUCCESS; +} + +int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + RT_NOREF(pBackend); + + LogFlowFunc(("fFormats=%02X\n", fFormats)); + + /** @todo r=bird: BUGBUG: The following is probably a mistake. */ + /** @todo r=andy: BUGBUG: Has been there since forever; needs investigation first before removing. */ + if (fFormats == VBOX_SHCL_FMT_NONE) + { + /* This is just an automatism, not a genuine announcement */ + return VINF_SUCCESS; + } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (fFormats & VBOX_SHCL_FMT_URI_LIST) /* No transfer support yet. */ + return VINF_SUCCESS; +#endif + + SHCLCONTEXT *pCtx = pClient->State.pCtx; + ShClSvcLock(); + + /* + * Generate a unique flavor string for this format announcement. + */ + uint64_t idFlavor = RTRandU64(); + pCtx->idGuestOwnership = idFlavor; + RTStrPrintf(pCtx->szGuestOwnershipFlavor, sizeof(pCtx->szGuestOwnershipFlavor), + "org.virtualbox.sharedclipboard.%RTproc.%RX64", RTProcSelf(), idFlavor); + + /* + * Empty the pasteboard and put our ownership indicator flavor there + * with the stringified formats as value. + */ + char szValue[32]; + RTStrPrintf(szValue, sizeof(szValue), "%#x", fFormats); + + takePasteboardOwnership(pCtx->hPasteboard, pCtx->idGuestOwnership, pCtx->szGuestOwnershipFlavor, szValue, + &pCtx->hStrOwnershipFlavor); + + ShClSvcUnlock(); + + /* + * Now, request the data from the guest. + */ + return ShClSvcGuestDataRequest(pClient, fFormats, NULL /* pidEvent */); +} + +int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT fFormat, + void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); + + RT_NOREF(pBackend, pCmdCtx); + + ShClSvcLock(); + + /* Default to no data available. */ + *pcbActual = 0; + + int rc = readFromPasteboard(pClient->State.pCtx->hPasteboard, fFormat, pvData, cbData, pcbActual); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Error reading host clipboard data from macOS, rc=%Rrc\n", rc)); + + ShClSvcUnlock(); + + return rc; +} + +int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT fFormat, void *pvData, uint32_t cbData) +{ + RT_NOREF(pBackend, pCmdCtx); + + LogFlowFuncEnter(); + + ShClSvcLock(); + + writeToPasteboard(pClient->State.pCtx->hPasteboard, pClient->State.pCtx->idGuestOwnership, pvData, cbData, fFormat); + + ShClSvcUnlock(); + + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + +int ShClBackendTransferReadDir(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLDIRDATA pDirData) +{ + RT_NOREF(pBackend, pClient, pDirData); + return VERR_NOT_IMPLEMENTED; +} + +int ShClBackendTransferWriteDir(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLDIRDATA pDirData) +{ + RT_NOREF(pBackend, pClient, pDirData); + return VERR_NOT_IMPLEMENTED; +} + +int ShClBackendTransferReadFileHdr(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLFILEHDR pFileHdr) +{ + RT_NOREF(pBackend, pClient, pFileHdr); + return VERR_NOT_IMPLEMENTED; +} + +int ShClBackendTransferWriteFileHdr(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLFILEHDR pFileHdr) +{ + RT_NOREF(pBackend, pClient, pFileHdr); + return VERR_NOT_IMPLEMENTED; +} + +int ShClBackendTransferReadFileData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLFILEDATA pFileData) +{ + RT_NOREF(pBackend, pClient, pFileData); + return VERR_NOT_IMPLEMENTED; +} + +int ShClBackendTransferWriteFileData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLFILEDATA pFileData) +{ + RT_NOREF(pBackend, pClient, pFileData); + return VERR_NOT_IMPLEMENTED; +} + +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h new file mode 100644 index 00000000..add48a0c --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h @@ -0,0 +1,531 @@ +/* $Id: VBoxSharedClipboardSvc-internal.h $ */ +/** @file + * Shared Clipboard Service - Internal header. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_SharedClipboard_VBoxSharedClipboardSvc_internal_h +#define VBOX_INCLUDED_SRC_SharedClipboard_VBoxSharedClipboardSvc_internal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <algorithm> +#include <list> +#include <map> + +#include <iprt/cpp/list.h> /* For RTCList. */ +#include <iprt/list.h> +#include <iprt/semaphore.h> + +#include <VBox/hgcmsvc.h> +#include <VBox/log.h> + +#include <VBox/HostServices/Service.h> +#include <VBox/GuestHost/SharedClipboard.h> +#include <VBox/GuestHost/SharedClipboard-transfers.h> + +using namespace HGCM; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +struct SHCLCLIENTSTATE; +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +/** + * A queued message for the guest. + */ +typedef struct _SHCLCLIENTMSG +{ + /** The queue list entry. */ + RTLISTNODE ListEntry; + /** Stored message ID (VBOX_SHCL_HOST_MSG_XXX). */ + uint32_t idMsg; + /** Context ID. */ + uint64_t idCtx; + /** Number of stored parameters in aParms. */ + uint32_t cParms; + /** HGCM parameters. */ + RT_FLEXIBLE_ARRAY_EXTENSION + VBOXHGCMSVCPARM aParms[RT_FLEXIBLE_ARRAY]; +} SHCLCLIENTMSG; +/** Pointer to a queue message for the guest. */ +typedef SHCLCLIENTMSG *PSHCLCLIENTMSG; + +typedef struct SHCLCLIENTTRANSFERSTATE +{ + /** Directory of the transfer to start. */ + SHCLTRANSFERDIR enmTransferDir; +} SHCLCLIENTTRANSFERSTATE, *PSHCLCLIENTTRANSFERSTATE; + +/** + * Structure for holding a single POD (plain old data) transfer. + * + * This mostly is plain text, but also can be stuff like bitmap (BMP) or other binary data. + */ +typedef struct SHCLCLIENTPODSTATE +{ + /** POD transfer direction. */ + SHCLTRANSFERDIR enmDir; + /** Format of the data to be read / written. */ + SHCLFORMAT uFormat; + /** How much data (in bytes) to read/write for the current operation. */ + uint64_t cbToReadWriteTotal; + /** How much data (in bytes) already has been read/written for the current operation. */ + uint64_t cbReadWritten; + /** Timestamp (in ms) of Last read/write operation. */ + uint64_t tsLastReadWrittenMs; +} SHCLCLIENTPODSTATE, *PSHCLCLIENTPODSTATE; + +/** @name SHCLCLIENTSTATE_FLAGS_XXX + * @note Part of saved state! + * @{ */ +/** No Shared Clipboard client flags defined. */ +#define SHCLCLIENTSTATE_FLAGS_NONE 0 +/** Client has a guest read operation active. Currently unused. */ +#define SHCLCLIENTSTATE_FLAGS_READ_ACTIVE RT_BIT(0) +/** Client has a guest write operation active. Currently unused. */ +#define SHCLCLIENTSTATE_FLAGS_WRITE_ACTIVE RT_BIT(1) +/** @} */ + +/** + * Structure needed to support backwards compatbility for old(er) Guest Additions (< 6.1), + * which did not know the context ID concept then. + */ +typedef struct SHCLCLIENTLEGACYCID +{ + /** List node. */ + RTLISTNODE Node; + /** The actual context ID. */ + uint64_t uCID; + /** Not used yet; useful to have it in the saved state though. */ + uint32_t enmType; + /** @todo Add an union here as soon as we utilize \a enmType. */ + SHCLFORMAT uFormat; +} SHCLCLIENTLEGACYCID; +/** Pointer to a SHCLCLIENTLEGACYCID struct. */ +typedef SHCLCLIENTLEGACYCID *PSHCLCLIENTLEGACYCID; + +/** + * Structure for keeping legacy state, required for keeping backwards compatibility + * to old(er) Guest Additions. + */ +typedef struct SHCLCLIENTLEGACYSTATE +{ + /** List of context IDs (of type SHCLCLIENTLEGACYCID) for older Guest Additions which (< 6.1) + * which did not know the concept of context IDs. */ + RTLISTANCHOR lstCID; + /** Number of context IDs currently in \a lstCID. */ + uint16_t cCID; +} SHCLCLIENTLEGACYSTATE; + +/** + * Structure for keeping generic client state data within the Shared Clipboard host service. + * This structure needs to be serializable by SSM (must be a POD type). + */ +typedef struct SHCLCLIENTSTATE +{ + struct SHCLCLIENTSTATE *pNext; + struct SHCLCLIENTSTATE *pPrev; + + /** Backend-dependent opaque context structure. + * This contains data only known to a certain backend implementation. + * Optional and can be NULL. */ + SHCLCONTEXT *pCtx; + /** The client's HGCM ID. Not related to the session ID below! */ + uint32_t uClientID; + /** The client's session ID. */ + SHCLSESSIONID uSessionID; + /** Guest feature flags, VBOX_SHCL_GF_0_XXX. */ + uint64_t fGuestFeatures0; + /** Guest feature flags, VBOX_SHCL_GF_1_XXX. */ + uint64_t fGuestFeatures1; + /** Chunk size to use for data transfers. */ + uint32_t cbChunkSize; + /** Where the transfer sources its data from. */ + SHCLSOURCE enmSource; + /** Client state flags of type SHCLCLIENTSTATE_FLAGS_. */ + uint32_t fFlags; + /** POD (plain old data) state. */ + SHCLCLIENTPODSTATE POD; + /** The client's transfers state. */ + SHCLCLIENTTRANSFERSTATE Transfers; +} SHCLCLIENTSTATE, *PSHCLCLIENTSTATE; + +typedef struct _SHCLCLIENTCMDCTX +{ + uint64_t uContextID; +} SHCLCLIENTCMDCTX, *PSHCLCLIENTCMDCTX; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Structure for keeping transfer-related data per HGCM client. + */ +typedef struct _SHCLIENTTRANSFERS +{ + /** Transfer context. */ + SHCLTRANSFERCTX Ctx; +} SHCLIENTTRANSFERS, *PSHCLIENTTRANSFERS; +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +/** Prototypes for the Shared Clipboard backend. */ +struct SHCLBACKEND; +typedef SHCLBACKEND *PSHCLBACKEND; + +/** + * Structure for keeping data per (connected) HGCM client. + */ +typedef struct _SHCLCLIENT +{ + /** Pointer to associated backend, if any. + * Might be NULL if not being used. */ + PSHCLBACKEND pBackend; + /** General client state data. */ + SHCLCLIENTSTATE State; + /** The critical section protecting the queue, event source and whatnot. */ + RTCRITSECT CritSect; + /** The client's message queue (SHCLCLIENTMSG). */ + RTLISTANCHOR MsgQueue; + /** Number of allocated messages (updated atomically, not under critsect). */ + uint32_t volatile cMsgAllocated; + /** Legacy cruft we have to keep to support old(er) Guest Additions. */ + SHCLCLIENTLEGACYSTATE Legacy; + /** The client's own event source. + * Needed for events which are not bound to a specific transfer. */ + SHCLEVENTSOURCE EventSrc; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + SHCLIENTTRANSFERS Transfers; +#endif + /** Structure for keeping the client's pending (deferred return) state. + * A client is in a deferred state when it asks for the next HGCM message, + * but the service can't provide it yet. That way a client will block (on the guest side, does not return) + * until the service can complete the call. */ + struct + { + /** The client's HGCM call handle. Needed for completing a deferred call. */ + VBOXHGCMCALLHANDLE hHandle; + /** Message type (function number) to use when completing the deferred call. + * A non-0 value means the client is in pending mode. */ + uint32_t uType; + /** Parameter count to use when completing the deferred call. */ + uint32_t cParms; + /** Parameters to use when completing the deferred call. */ + PVBOXHGCMSVCPARM paParms; + } Pending; +} SHCLCLIENT, *PSHCLCLIENT; + +/** + * Structure for keeping a single event source map entry. + * Currently empty. + */ +typedef struct _SHCLEVENTSOURCEMAPENTRY +{ +} SHCLEVENTSOURCEMAPENTRY; + +/** Map holding information about connected HGCM clients. Key is the (unique) HGCM client ID. + * The value is a weak pointer to PSHCLCLIENT, which is owned by HGCM. */ +typedef std::map<uint32_t, PSHCLCLIENT> ClipboardClientMap; + +/** Map holding information about event sources. Key is the (unique) event source ID. */ +typedef std::map<SHCLEVENTSOURCEID, SHCLEVENTSOURCEMAPENTRY> ClipboardEventSourceMap; + +/** Simple queue (list) which holds deferred (waiting) clients. */ +typedef std::list<uint32_t> ClipboardClientQueue; + +/** + * Structure for keeping the Shared Clipboard service extension state. + * + * A service extension is optional, and can be installed by a host component + * to communicate with the Shared Clipboard host service. + */ +typedef struct _SHCLEXTSTATE +{ + /** Pointer to the actual service extension handle. */ + PFNHGCMSVCEXT pfnExtension; + /** Opaque pointer to extension-provided data. Don't touch. */ + void *pvExtension; + /** The HGCM client ID currently assigned to this service extension. + * At the moment only one HGCM client can be assigned per extension. */ + uint32_t uClientID; + /** Whether the host service is reading clipboard data currently. */ + bool fReadingData; + /** Whether the service extension has sent the clipboard formats while + * the the host service is reading clipboard data from it. */ + bool fDelayedAnnouncement; + /** The actual clipboard formats announced while the host service + * is reading clipboard data from the extension. */ + uint32_t fDelayedFormats; +} SHCLEXTSTATE, *PSHCLEXTSTATE; + +extern SHCLEXTSTATE g_ExtState; + +int shClSvcSetSource(PSHCLCLIENT pClient, SHCLSOURCE enmSource); + +void shClSvcMsgQueueReset(PSHCLCLIENT pClient); +PSHCLCLIENTMSG shClSvcMsgAlloc(PSHCLCLIENT pClient, uint32_t uMsg, uint32_t cParms); +void shClSvcMsgFree(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg); +void shClSvcMsgAdd(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg, bool fAppend); +int shClSvcMsgAddAndWakeupClient(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg); + +int shClSvcClientInit(PSHCLCLIENT pClient, uint32_t uClientID); +void shClSvcClientDestroy(PSHCLCLIENT pClient); +void shClSvcClientLock(PSHCLCLIENT pClient); +void shClSvcClientUnlock(PSHCLCLIENT pClient); +void shClSvcClientReset(PSHCLCLIENT pClient); + +int shClSvcClientStateInit(PSHCLCLIENTSTATE pClientState, uint32_t uClientID); +int shClSvcClientStateDestroy(PSHCLCLIENTSTATE pClientState); +void shclSvcClientStateReset(PSHCLCLIENTSTATE pClientState); + +int shClSvcClientWakeup(PSHCLCLIENT pClient); + +# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +int shClSvcTransferModeSet(uint32_t fMode); +int shClSvcTransferStart(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, PSHCLTRANSFER *ppTransfer); +int shClSvcTransferStop(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); +bool shClSvcTransferMsgIsAllowed(uint32_t uMode, uint32_t uMsg); +void shClSvcClientTransfersReset(PSHCLCLIENT pClient); +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +/** @name Service functions, accessible by the backends. + * Locking is between the (host) service thread and the platform-dependent (window) thread. + * @{ + */ +int ShClSvcGuestDataRequest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, PSHCLEVENT *ppEvent); +int ShClSvcGuestDataSignal(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); +int ShClSvcHostReportFormats(PSHCLCLIENT pClient, SHCLFORMATS fFormats); +PSHCLBACKEND ShClSvcGetBackend(void); +uint32_t ShClSvcGetMode(void); +bool ShClSvcGetHeadless(void); +bool ShClSvcLock(void); +void ShClSvcUnlock(void); + +/** + * Checks if the backend is active (@c true), or if VRDE is in control of + * the host side. + */ +DECLINLINE(bool) ShClSvcIsBackendActive(void) +{ + return g_ExtState.pfnExtension == NULL; +} +/** @} */ + +/** @name Platform-dependent implementations for the Shared Clipboard host service ("backends"), + * called *only* by the host service. + * @{ + */ +/** + * Structure for keeping Shared Clipboard backend instance data. + */ +typedef struct SHCLBACKEND +{ + /** Callback table to use. + * Some callbacks might be optional and therefore NULL -- see the table for more details. */ + SHCLCALLBACKS Callbacks; +} SHCLBACKEND; +/** Pointer to a Shared Clipboard backend. */ +typedef SHCLBACKEND *PSHCLBACKEND; + +/** + * Called on initialization. + * + * @param pBackend Shared Clipboard backend to initialize. + * @param pTable The HGCM service call and parameter table. Mainly for + * adjusting the limits. + */ +int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable); + +/** + * Called on destruction. + * + * @param pBackend Shared Clipboard backend to destroy. + */ +void ShClBackendDestroy(PSHCLBACKEND pBackend); + +/** + * Called when a new HGCM client connects. + * + * @param pBackend Shared Clipboard backend to set callbacks for. + * @param pCallbacks Backend callbacks to use. + * When NULL is specified, the backend's default callbacks are being used. + */ +void ShClBackendSetCallbacks(PSHCLBACKEND pBackend, PSHCLCALLBACKS pCallbacks); + +/** + * Called when a new HGCM client connects. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to connect to. + * @param pClient Shared Clipboard client context. + * @param fHeadless Whether this is a headless connection or not. + */ +int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless); + +/** + * Called when a HGCM client disconnects. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to disconnect from. + * @param pClient Shared Clipboard client context. + */ +int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient); + +/** + * Called when the guest reports available clipboard formats to the host OS. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to announce formats to. + * @param pClient Shared Clipboard client context. + * @param fFormats The announced formats from the guest, + * VBOX_SHCL_FMT_XXX. + */ +int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats); + +/** + * Called when the guest wants to read host clipboard data. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to read data from. + * @param pClient Shared Clipboard client context. + * @param pCmdCtx Shared Clipboard command context. + * @param uFormat Clipboard format to read. + * @param pvData Where to return the read clipboard data. + * @param cbData Size (in bytes) of buffer where to return the clipboard data. + * @param pcbActual Where to return the amount of bytes read. + * + * @todo Document: Can return VINF_HGCM_ASYNC_EXECUTE to defer returning read + * data + */ +int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, + void *pvData, uint32_t cbData, uint32_t *pcbActual); + +/** + * Called when the guest writes clipboard data to the host. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to write data to. + * @param pClient Shared Clipboard client context. + * @param pCmdCtx Shared Clipboard command context. + * @param uFormat Clipboard format to write. + * @param pvData Clipboard data to write. + * @param cbData Size (in bytes) of buffer clipboard data to write. + */ +int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); + +/** + * Called when synchronization of the clipboard contents of the host clipboard with the guest is needed. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to synchronize. + * @param pClient Shared Clipboard client context. + */ +int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient); +/** @} */ + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** @name Host implementations for Shared Clipboard transfers. + * @{ + */ +/** + * Called after a transfer got created. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to use. + * @param pClient Shared Clipboard client context. + * @param pTransfer Shared Clipboard transfer created. + */ +int ShClBackendTransferCreate(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); +/** + * Called before a transfer gets destroyed. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to use. + * @param pClient Shared Clipboard client context. + * @param pTransfer Shared Clipboard transfer to destroy. + */ +int ShClBackendTransferDestroy(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); +/** + * Called when getting (determining) the transfer roots on the host side. + * + * @returns VBox status code. + * @param pBackend Shared Clipboard backend to use. + * @param pClient Shared Clipboard client context. + * @param pTransfer Shared Clipboard transfer to get roots for. + */ +int ShClBackendTransferGetRoots(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); +/** @} */ +#endif + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** @name Internal Shared Clipboard transfer host service functions. + * @{ + */ +int shClSvcTransferHandler(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE callHandle, uint32_t u32Function, + uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival); +int shClSvcTransferHostHandler(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); +/** @} */ + +/** @name Shared Clipboard transfer interface implementations for the host service. + * @{ + */ +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ + +int shClSvcTransferIfaceGetRoots(PSHCLTXPROVIDERCTX pCtx, PSHCLROOTLIST *ppRootList); + +int shClSvcTransferIfaceListOpen(PSHCLTXPROVIDERCTX pCtx, PSHCLLISTOPENPARMS pOpenParms, PSHCLLISTHANDLE phList); +int shClSvcTransferIfaceListClose(PSHCLTXPROVIDERCTX pCtx, SHCLLISTHANDLE hList); +int shClSvcTransferIfaceListHdrRead(PSHCLTXPROVIDERCTX pCtx, SHCLLISTHANDLE hList, PSHCLLISTHDR pListHdr); +int shClSvcTransferIfaceListHdrWrite(PSHCLTXPROVIDERCTX pCtx, SHCLLISTHANDLE hList, PSHCLLISTHDR pListHdr); +int shClSvcTransferIfaceListEntryRead(PSHCLTXPROVIDERCTX pCtx, SHCLLISTHANDLE hList, PSHCLLISTENTRY pListEntry); +int shClSvcTransferIfaceListEntryWrite(PSHCLTXPROVIDERCTX pCtx, SHCLLISTHANDLE hList, PSHCLLISTENTRY pListEntry); + +int shClSvcTransferIfaceObjOpen(PSHCLTXPROVIDERCTX pCtx, PSHCLOBJOPENCREATEPARMS pCreateParms, + PSHCLOBJHANDLE phObj); +int shClSvcTransferIfaceObjClose(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj); +int shClSvcTransferIfaceObjRead(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj, + void *pvData, uint32_t cbData, uint32_t fFlags, uint32_t *pcbRead); +int shClSvcTransferIfaceObjWrite(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj, + void *pvData, uint32_t cbData, uint32_t fFlags, uint32_t *pcbWritten); +/** @} */ + +/** @name Shared Clipboard transfer callbacks for the host service. + * @{ + */ +DECLCALLBACK(void) VBoxSvcClipboardTransferPrepareCallback(PSHCLTXPROVIDERCTX pCtx); +DECLCALLBACK(void) VBoxSvcClipboardDataHeaderCompleteCallback(PSHCLTXPROVIDERCTX pCtx); +DECLCALLBACK(void) VBoxSvcClipboardDataCompleteCallback(PSHCLTXPROVIDERCTX pCtx); +DECLCALLBACK(void) VBoxSvcClipboardTransferCompleteCallback(PSHCLTXPROVIDERCTX pCtx, int rc); +DECLCALLBACK(void) VBoxSvcClipboardTransferCanceledCallback(PSHCLTXPROVIDERCTX pCtx); +DECLCALLBACK(void) VBoxSvcClipboardTransferErrorCallback(PSHCLTXPROVIDERCTX pCtx, int rc); +/** @} */ +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +/* Host unit testing interface */ +#ifdef UNIT_TEST +uint32_t TestClipSvcGetMode(void); +#endif + +#endif /* !VBOX_INCLUDED_SRC_SharedClipboard_VBoxSharedClipboardSvc_internal_h */ + diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp new file mode 100644 index 00000000..3f36f170 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp @@ -0,0 +1,2067 @@ +/* $Id: VBoxSharedClipboardSvc-transfers.cpp $ */ +/** @file + * Shared Clipboard Service - Internal code for transfer (list) handling. + */ + +/* + * Copyright (C) 2019-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <VBox/log.h> + +#include <VBox/err.h> + +#include <VBox/GuestHost/clipboard-helper.h> +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <VBox/HostServices/VBoxClipboardExt.h> + +#include <VBox/AssertGuest.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/path.h> + +#include "VBoxSharedClipboardSvc-internal.h" +#include "VBoxSharedClipboardSvc-transfers.h" + + +/********************************************************************************************************************************* +* Externals * +*********************************************************************************************************************************/ +extern uint32_t g_fTransferMode; +extern SHCLEXTSTATE g_ExtState; +extern PVBOXHGCMSVCHELPERS g_pHelpers; +extern ClipboardClientMap g_mapClients; +extern ClipboardClientQueue g_listClientsDeferred; + + +/********************************************************************************************************************************* +* Prototypes * +*********************************************************************************************************************************/ +static int shClSvcTransferSetListOpen(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + uint64_t idCtx, PSHCLLISTOPENPARMS pOpenParms); +static int shClSvcTransferSetListClose(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + uint64_t idCtx, SHCLLISTHANDLE hList); + + +/********************************************************************************************************************************* +* Provider implementation * +*********************************************************************************************************************************/ + +/** + * Resets all transfers of a Shared Clipboard client. + * + * @param pClient Client to reset transfers for. + */ +void shClSvcClientTransfersReset(PSHCLCLIENT pClient) +{ + if (!pClient) + return; + + LogFlowFuncEnter(); + + /* Make sure to let the backend know that all transfers are getting destroyed. */ + uint32_t uIdx = 0; + PSHCLTRANSFER pTransfer; + while ((pTransfer = ShClTransferCtxGetTransferByIndex(&pClient->Transfers.Ctx, uIdx++))) + ShClBackendTransferDestroy(pClient->pBackend, pClient, pTransfer); + + ShClTransferCtxDestroy(&pClient->Transfers.Ctx); +} + + +/********************************************************************************************************************************* +* Provider interface implementation * +*********************************************************************************************************************************/ + +DECLCALLBACK(int) shClSvcTransferIfaceGetRoots(PSHCLTXPROVIDERCTX pCtx, PSHCLROOTLIST *ppRootList) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsgHdr = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_READ, + VBOX_SHCL_CPARMS_ROOT_LIST_HDR_READ_REQ); + if (pMsgHdr) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsgHdr->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU32(&pMsgHdr->aParms[1], 0 /* fRoots */); + + shClSvcClientLock(pClient); + + shClSvcMsgAdd(pClient, pMsgHdr, true /* fAppend */); + rc = shClSvcClientWakeup(pClient); + + shClSvcClientUnlock(pClient); + + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayloadHdr; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayloadHdr); + if (RT_SUCCESS(rc)) + { + PSHCLROOTLISTHDR pSrcRootListHdr = (PSHCLROOTLISTHDR)pPayloadHdr->pvData; + Assert(pPayloadHdr->cbData == sizeof(SHCLROOTLISTHDR)); + + LogFlowFunc(("cRoots=%RU32, fRoots=0x%x\n", pSrcRootListHdr->cRoots, pSrcRootListHdr->fRoots)); + + PSHCLROOTLIST pRootList = ShClTransferRootListAlloc(); + if (pRootList) + { + if (pSrcRootListHdr->cRoots) + { + pRootList->paEntries = + (PSHCLROOTLISTENTRY)RTMemAllocZ(pSrcRootListHdr->cRoots * sizeof(SHCLROOTLISTENTRY)); + + if (pRootList->paEntries) + { + for (uint32_t i = 0; i < pSrcRootListHdr->cRoots; i++) + { + PSHCLCLIENTMSG pMsgEntry = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_READ, + VBOX_SHCL_CPARMS_ROOT_LIST_ENTRY_READ_REQ); + + PSHCLEVENT pEvRoot; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvRoot); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsgEntry->aParms[0], + VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uClientID, + pCtx->pTransfer->State.uID, pEvRoot->idEvent)); + HGCMSvcSetU32(&pMsgEntry->aParms[1], 0 /* fRoots */); + HGCMSvcSetU32(&pMsgEntry->aParms[2], i /* uIndex */); + + shClSvcClientLock(pClient); + shClSvcMsgAdd(pClient, pMsgEntry, true /* fAppend */); + shClSvcClientUnlock(pClient); + + PSHCLEVENTPAYLOAD pPayloadEntry; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayloadEntry); + if (RT_FAILURE(rc)) + break; + + PSHCLROOTLISTENTRY pSrcRootListEntry = (PSHCLROOTLISTENTRY)pPayloadEntry->pvData; + Assert(pPayloadEntry->cbData == sizeof(SHCLROOTLISTENTRY)); + + rc = ShClTransferListEntryCopy(&pRootList->paEntries[i], pSrcRootListEntry); + + ShClPayloadFree(pPayloadEntry); + + ShClEventRelease(pEvRoot); + pEvRoot = NULL; + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + + if (RT_FAILURE(rc)) + break; + } + } + else + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + pRootList->Hdr.cRoots = pSrcRootListHdr->cRoots; + pRootList->Hdr.fRoots = 0; /** @todo Implement this. */ + + *ppRootList = pRootList; + } + else + ShClTransferRootListFree(pRootList); + + ShClPayloadFree(pPayloadHdr); + } + else + rc = VERR_NO_MEMORY; + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeave(); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListOpen(PSHCLTXPROVIDERCTX pCtx, + PSHCLLISTOPENPARMS pOpenParms, PSHCLLISTHANDLE phList) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_LIST_OPEN, + VBOX_SHCL_CPARMS_LIST_OPEN); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + pMsg->idCtx = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pCtx->pTransfer->State.uID, + pEvent->idEvent); + + rc = shClSvcTransferSetListOpen(pMsg->cParms, pMsg->aParms, pMsg->idCtx, pOpenParms); + if (RT_SUCCESS(rc)) + { + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLREPLY)); + + PSHCLREPLY pReply = (PSHCLREPLY)pPayload->pvData; + AssertPtr(pReply); + + Assert(pReply->uType == VBOX_SHCL_REPLYMSGTYPE_LIST_OPEN); + + LogFlowFunc(("hList=%RU64\n", pReply->u.ListOpen.uHandle)); + + *phList = pReply->u.ListOpen.uHandle; + + ShClPayloadFree(pPayload); + } + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListClose(PSHCLTXPROVIDERCTX pCtx, SHCLLISTHANDLE hList) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_LIST_CLOSE, + VBOX_SHCL_CPARMS_LIST_CLOSE); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + pMsg->idCtx = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pCtx->pTransfer->State.uID, + pEvent->idEvent); + + rc = shClSvcTransferSetListClose(pMsg->cParms, pMsg->aParms, pMsg->idCtx, hList); + if (RT_SUCCESS(rc)) + { + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + ShClPayloadFree(pPayload); + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListHdrRead(PSHCLTXPROVIDERCTX pCtx, + SHCLLISTHANDLE hList, PSHCLLISTHDR pListHdr) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_READ, + VBOX_SHCL_CPARMS_LIST_HDR_READ_REQ); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU64(&pMsg->aParms[1], hList); + HGCMSvcSetU32(&pMsg->aParms[2], 0 /* fFlags */); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLLISTHDR)); + + *pListHdr = *(PSHCLLISTHDR)pPayload->pvData; + + ShClPayloadFree(pPayload); + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListHdrWrite(PSHCLTXPROVIDERCTX pCtx, + SHCLLISTHANDLE hList, PSHCLLISTHDR pListHdr) +{ + RT_NOREF(pCtx, hList, pListHdr); + + LogFlowFuncEnter(); + + return VERR_NOT_IMPLEMENTED; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListEntryRead(PSHCLTXPROVIDERCTX pCtx, + SHCLLISTHANDLE hList, PSHCLLISTENTRY pListEntry) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_READ, + VBOX_SHCL_CPARMS_LIST_ENTRY_READ); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU64(&pMsg->aParms[1], hList); + HGCMSvcSetU32(&pMsg->aParms[2], 0 /* fInfo */); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLLISTENTRY)); + + rc = ShClTransferListEntryCopy(pListEntry, (PSHCLLISTENTRY)pPayload->pvData); + + ShClPayloadFree(pPayload); + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +DECLCALLBACK(int) shClSvcTransferIfaceListEntryWrite(PSHCLTXPROVIDERCTX pCtx, + SHCLLISTHANDLE hList, PSHCLLISTENTRY pListEntry) +{ + RT_NOREF(pCtx, hList, pListEntry); + + LogFlowFuncEnter(); + + return VERR_NOT_IMPLEMENTED; +} + +int shClSvcTransferIfaceObjOpen(PSHCLTXPROVIDERCTX pCtx, PSHCLOBJOPENCREATEPARMS pCreateParms, + PSHCLOBJHANDLE phObj) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_OPEN, + VBOX_SHCL_CPARMS_OBJ_OPEN); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("pszPath=%s, fCreate=0x%x\n", pCreateParms->pszPath, pCreateParms->fCreate)); + + const uint32_t cbPath = (uint32_t)strlen(pCreateParms->pszPath) + 1; /* Include terminating zero */ + + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU64(&pMsg->aParms[1], 0); /* uHandle */ + HGCMSvcSetU32(&pMsg->aParms[2], cbPath); + HGCMSvcSetPv (&pMsg->aParms[3], pCreateParms->pszPath, cbPath); + HGCMSvcSetU32(&pMsg->aParms[4], pCreateParms->fCreate); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLREPLY)); + + PSHCLREPLY pReply = (PSHCLREPLY)pPayload->pvData; + AssertPtr(pReply); + + Assert(pReply->uType == VBOX_SHCL_REPLYMSGTYPE_OBJ_OPEN); + + LogFlowFunc(("hObj=%RU64\n", pReply->u.ObjOpen.uHandle)); + + *phObj = pReply->u.ObjOpen.uHandle; + + ShClPayloadFree(pPayload); + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int shClSvcTransferIfaceObjClose(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_CLOSE, + VBOX_SHCL_CPARMS_OBJ_CLOSE); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU64(&pMsg->aParms[1], hObj); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLREPLY)); +#ifdef VBOX_STRICT + PSHCLREPLY pReply = (PSHCLREPLY)pPayload->pvData; + AssertPtr(pReply); + + Assert(pReply->uType == VBOX_SHCL_REPLYMSGTYPE_OBJ_CLOSE); + + LogFlowFunc(("hObj=%RU64\n", pReply->u.ObjClose.uHandle)); +#endif + ShClPayloadFree(pPayload); + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int shClSvcTransferIfaceObjRead(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj, + void *pvData, uint32_t cbData, uint32_t fFlags, uint32_t *pcbRead) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_READ, + VBOX_SHCL_CPARMS_OBJ_READ_REQ); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU64(&pMsg->aParms[1], hObj); + HGCMSvcSetU32(&pMsg->aParms[2], cbData); + HGCMSvcSetU32(&pMsg->aParms[3], fFlags); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLOBJDATACHUNK)); + + PSHCLOBJDATACHUNK pDataChunk = (PSHCLOBJDATACHUNK)pPayload->pvData; + AssertPtr(pDataChunk); + + const uint32_t cbRead = RT_MIN(cbData, pDataChunk->cbData); + + memcpy(pvData, pDataChunk->pvData, cbRead); + + if (pcbRead) + *pcbRead = cbRead; + + ShClPayloadFree(pPayload); + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int shClSvcTransferIfaceObjWrite(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj, + void *pvData, uint32_t cbData, uint32_t fFlags, uint32_t *pcbWritten) +{ + LogFlowFuncEnter(); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; + AssertPtr(pClient); + + int rc; + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_WRITE, + VBOX_SHCL_CPARMS_OBJ_WRITE); + if (pMsg) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsg->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pCtx->pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU64(&pMsg->aParms[1], hObj); + HGCMSvcSetU64(&pMsg->aParms[2], cbData); + HGCMSvcSetU64(&pMsg->aParms[3], fFlags); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, pCtx->pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + const uint32_t cbRead = RT_MIN(cbData, pPayload->cbData); + + memcpy(pvData, pPayload->pvData, cbRead); + + if (pcbWritten) + *pcbWritten = cbRead; + + ShClPayloadFree(pPayload); + } + } + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/********************************************************************************************************************************* +* HGCM getters / setters * +*********************************************************************************************************************************/ + +/** + * Returns whether a HGCM message is allowed in a certain service mode or not. + * + * @returns \c true if message is allowed, \c false if not. + * @param uMode Service mode to check allowance for. + * @param uMsg HGCM message to check allowance for. + */ +bool shClSvcTransferMsgIsAllowed(uint32_t uMode, uint32_t uMsg) +{ + const bool fHostToGuest = uMode == VBOX_SHCL_MODE_HOST_TO_GUEST + || uMode == VBOX_SHCL_MODE_BIDIRECTIONAL; + + const bool fGuestToHost = uMode == VBOX_SHCL_MODE_GUEST_TO_HOST + || uMode == VBOX_SHCL_MODE_BIDIRECTIONAL; + + bool fAllowed = false; /* If in doubt, don't allow. */ + + switch (uMsg) + { + case VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_WRITE: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_WRITE: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_LIST_HDR_WRITE: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_LIST_ENTRY_WRITE: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_OBJ_WRITE: + fAllowed = fGuestToHost; + break; + + case VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_READ: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_READ: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_LIST_HDR_READ: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_LIST_ENTRY_READ: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_OBJ_READ: + fAllowed = fHostToGuest; + break; + + case VBOX_SHCL_GUEST_FN_CONNECT: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_NEGOTIATE_CHUNK_SIZE: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_REPORT_FEATURES: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_QUERY_FEATURES: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_MSG_GET: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_REPLY: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_MSG_CANCEL: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_ERROR: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_LIST_OPEN: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_LIST_CLOSE: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_OBJ_OPEN: + RT_FALL_THROUGH(); + case VBOX_SHCL_GUEST_FN_OBJ_CLOSE: + fAllowed = fHostToGuest || fGuestToHost; + break; + + default: + break; + } + + LogFlowFunc(("uMsg=%RU32 (%s), uMode=%RU32 -> fAllowed=%RTbool\n", uMsg, ShClGuestMsgToStr(uMsg), uMode, fAllowed)); + return fAllowed; +} + +/** + * Gets a transfer message reply from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param pReply Where to store the reply. + */ +static int shClSvcTransferGetReply(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + PSHCLREPLY pReply) +{ + int rc; + + if (cParms >= VBOX_SHCL_CPARMS_REPLY_MIN) + { + /* aParms[0] has the context ID. */ + rc = HGCMSvcGetU32(&aParms[1], &pReply->uType); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&aParms[2], &pReply->rc); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&aParms[3], &pReply->pvPayload, &pReply->cbPayload); + + if (RT_SUCCESS(rc)) + { + rc = VERR_INVALID_PARAMETER; /* Play safe. */ + + const unsigned idxParm = VBOX_SHCL_CPARMS_REPLY_MIN; + + switch (pReply->uType) + { + case VBOX_SHCL_REPLYMSGTYPE_TRANSFER_STATUS: + { + if (cParms > idxParm) + rc = HGCMSvcGetU32(&aParms[idxParm], &pReply->u.TransferStatus.uStatus); + + LogFlowFunc(("uTransferStatus=%RU32\n", pReply->u.TransferStatus.uStatus)); + break; + } + + case VBOX_SHCL_REPLYMSGTYPE_LIST_OPEN: + { + if (cParms > idxParm) + rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ListOpen.uHandle); + + LogFlowFunc(("hListOpen=%RU64\n", pReply->u.ListOpen.uHandle)); + break; + } + + case VBOX_SHCL_REPLYMSGTYPE_LIST_CLOSE: + { + if (cParms > idxParm) + rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ListClose.uHandle); + + LogFlowFunc(("hListClose=%RU64\n", pReply->u.ListClose.uHandle)); + break; + } + + case VBOX_SHCL_REPLYMSGTYPE_OBJ_OPEN: + { + if (cParms > idxParm) + rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ObjOpen.uHandle); + + LogFlowFunc(("hObjOpen=%RU64\n", pReply->u.ObjOpen.uHandle)); + break; + } + + case VBOX_SHCL_REPLYMSGTYPE_OBJ_CLOSE: + { + if (cParms > idxParm) + rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ObjClose.uHandle); + + LogFlowFunc(("hObjClose=%RU64\n", pReply->u.ObjClose.uHandle)); + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + } + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Gets a transfer root list header from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param pRootLstHdr Where to store the transfer root list header on success. + */ +static int shClSvcTransferGetRootListHdr(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + PSHCLROOTLISTHDR pRootLstHdr) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_ROOT_LIST_HDR_WRITE) + { + rc = HGCMSvcGetU32(&aParms[1], &pRootLstHdr->fRoots); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&aParms[2], &pRootLstHdr->cRoots); + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Gets a transfer root list entry from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param pListEntry Where to store the root list entry. + */ +static int shClSvcTransferGetRootListEntry(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + PSHCLROOTLISTENTRY pListEntry) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_ROOT_LIST_ENTRY_WRITE) + { + rc = HGCMSvcGetU32(&aParms[1], &pListEntry->fInfo); + /* Note: aParms[2] contains the entry index, currently being ignored. */ + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&aParms[3], (void **)&pListEntry->pszName, &pListEntry->cbName); + if (RT_SUCCESS(rc)) + { + uint32_t cbInfo; + rc = HGCMSvcGetU32(&aParms[4], &cbInfo); + if (RT_SUCCESS(rc)) + { + rc = HGCMSvcGetPv(&aParms[5], &pListEntry->pvInfo, &pListEntry->cbInfo); + AssertReturn(cbInfo == pListEntry->cbInfo, VERR_INVALID_PARAMETER); + } + } + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Gets a transfer list open request from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param pOpenParms Where to store the open parameters of the request. + */ +static int shClSvcTransferGetListOpen(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + PSHCLLISTOPENPARMS pOpenParms) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_LIST_OPEN) + { + rc = HGCMSvcGetU32(&aParms[1], &pOpenParms->fList); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetStr(&aParms[2], &pOpenParms->pszFilter, &pOpenParms->cbFilter); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetStr(&aParms[3], &pOpenParms->pszPath, &pOpenParms->cbPath); + + /** @todo Some more validation. */ + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sets a transfer list open request to HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param idCtx Context ID to use. + * @param pOpenParms List open parameters to set. + */ +static int shClSvcTransferSetListOpen(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + uint64_t idCtx, PSHCLLISTOPENPARMS pOpenParms) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_LIST_OPEN) + { + HGCMSvcSetU64(&aParms[0], idCtx); + HGCMSvcSetU32(&aParms[1], pOpenParms->fList); + HGCMSvcSetPv (&aParms[2], pOpenParms->pszFilter, pOpenParms->cbFilter); + HGCMSvcSetPv (&aParms[3], pOpenParms->pszPath, pOpenParms->cbPath); + HGCMSvcSetU64(&aParms[4], 0); /* OUT: uHandle */ + + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sets a transfer list close request to HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param idCtx Context ID to use. + * @param hList Handle of list to close. + */ +static int shClSvcTransferSetListClose(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + uint64_t idCtx, SHCLLISTHANDLE hList) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_LIST_CLOSE) + { + HGCMSvcSetU64(&aParms[0], idCtx); + HGCMSvcSetU64(&aParms[1], hList); + + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Gets a transfer list header from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param phList Where to store the list handle. + * @param pListHdr Where to store the list header. + */ +static int shClSvcTransferGetListHdr(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + PSHCLLISTHANDLE phList, PSHCLLISTHDR pListHdr) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_LIST_HDR) + { + rc = HGCMSvcGetU64(&aParms[1], phList); + /* Note: Flags (aParms[2]) not used here. */ + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&aParms[3], &pListHdr->fFeatures); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU64(&aParms[4], &pListHdr->cTotalObjects); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU64(&aParms[5], &pListHdr->cbTotalSize); + + if (RT_SUCCESS(rc)) + { + /** @todo Validate pvMetaFmt + cbMetaFmt. */ + /** @todo Validate header checksum. */ + } + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sets a transfer list header to HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param pListHdr Pointer to list header to set. + */ +static int shClSvcTransferSetListHdr(uint32_t cParms, VBOXHGCMSVCPARM aParms[], PSHCLLISTHDR pListHdr) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_LIST_HDR) + { + /** @todo Set pvMetaFmt + cbMetaFmt. */ + /** @todo Calculate header checksum. */ + + HGCMSvcSetU32(&aParms[3], pListHdr->fFeatures); + HGCMSvcSetU64(&aParms[4], pListHdr->cTotalObjects); + HGCMSvcSetU64(&aParms[5], pListHdr->cbTotalSize); + + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Gets a transfer list entry from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param phList Where to store the list handle. + * @param pListEntry Where to store the list entry. + */ +static int shClSvcTransferGetListEntry(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + PSHCLLISTHANDLE phList, PSHCLLISTENTRY pListEntry) +{ + int rc; + + if (cParms == VBOX_SHCL_CPARMS_LIST_ENTRY) + { + rc = HGCMSvcGetU64(&aParms[1], phList); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&aParms[2], &pListEntry->fInfo); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&aParms[3], (void **)&pListEntry->pszName, &pListEntry->cbName); + if (RT_SUCCESS(rc)) + { + uint32_t cbInfo; + rc = HGCMSvcGetU32(&aParms[4], &cbInfo); + if (RT_SUCCESS(rc)) + { + rc = HGCMSvcGetPv(&aParms[5], &pListEntry->pvInfo, &pListEntry->cbInfo); + AssertReturn(cbInfo == pListEntry->cbInfo, VERR_INVALID_PARAMETER); + } + } + + if (RT_SUCCESS(rc)) + { + if (!ShClTransferListEntryIsValid(pListEntry)) + rc = VERR_INVALID_PARAMETER; + } + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sets a Shared Clipboard list entry to HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param pListEntry Pointer list entry to set. + */ +static int shClSvcTransferSetListEntry(uint32_t cParms, VBOXHGCMSVCPARM aParms[], + PSHCLLISTENTRY pListEntry) +{ + int rc; + + /* Sanity. */ + AssertReturn(ShClTransferListEntryIsValid(pListEntry), VERR_INVALID_PARAMETER); + + if (cParms == VBOX_SHCL_CPARMS_LIST_ENTRY) + { + HGCMSvcSetPv (&aParms[3], pListEntry->pszName, pListEntry->cbName); + HGCMSvcSetU32(&aParms[4], pListEntry->cbInfo); + HGCMSvcSetPv (&aParms[5], pListEntry->pvInfo, pListEntry->cbInfo); + + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Gets a transfer object data chunk from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a aParms. + * @param aParms Array of HGCM parameters. + * @param pDataChunk Where to store the object data chunk data. + */ +static int shClSvcTransferGetObjDataChunk(uint32_t cParms, VBOXHGCMSVCPARM aParms[], PSHCLOBJDATACHUNK pDataChunk) +{ + AssertPtrReturn(aParms, VERR_INVALID_PARAMETER); + AssertPtrReturn(pDataChunk, VERR_INVALID_PARAMETER); + + int rc; + + if (cParms == VBOX_SHCL_CPARMS_OBJ_WRITE) + { + rc = HGCMSvcGetU64(&aParms[1], &pDataChunk->uHandle); + if (RT_SUCCESS(rc)) + { + uint32_t cbData; + rc = HGCMSvcGetU32(&aParms[2], &cbData); + if (RT_SUCCESS(rc)) + { + rc = HGCMSvcGetPv(&aParms[3], &pDataChunk->pvData, &pDataChunk->cbData); + AssertReturn(cbData == pDataChunk->cbData, VERR_INVALID_PARAMETER); + + /** @todo Implement checksum handling. */ + } + } + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Handles a guest reply (VBOX_SHCL_GUEST_FN_REPLY) message. + * + * @returns VBox status code. + * @param pClient Pointer to associated client. + * @param pTransfer Pointer to transfer to handle guest reply for. + * @param cParms Number of function parameters supplied. + * @param aParms Array function parameters supplied. + */ +static int shClSvcTransferHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, + uint32_t cParms, VBOXHGCMSVCPARM aParms[]) +{ + RT_NOREF(pClient, pTransfer); + + int rc; + + uint32_t cbReply = sizeof(SHCLREPLY); + PSHCLREPLY pReply = (PSHCLREPLY)RTMemAlloc(cbReply); + if (pReply) + { + rc = shClSvcTransferGetReply(cParms, aParms, pReply); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload + = (PSHCLEVENTPAYLOAD)RTMemAlloc(sizeof(SHCLEVENTPAYLOAD)); + if (pPayload) + { + pPayload->pvData = pReply; + pPayload->cbData = cbReply; + + switch (pReply->uType) + { + case VBOX_SHCL_REPLYMSGTYPE_TRANSFER_STATUS: + RT_FALL_THROUGH(); + case VBOX_SHCL_REPLYMSGTYPE_LIST_OPEN: + RT_FALL_THROUGH(); + case VBOX_SHCL_REPLYMSGTYPE_LIST_CLOSE: + RT_FALL_THROUGH(); + case VBOX_SHCL_REPLYMSGTYPE_OBJ_OPEN: + RT_FALL_THROUGH(); + case VBOX_SHCL_REPLYMSGTYPE_OBJ_CLOSE: + { + uint64_t uCID; + rc = HGCMSvcGetU64(&aParms[0], &uCID); + if (RT_SUCCESS(rc)) + { + const PSHCLEVENT pEvent + = ShClEventSourceGetFromId(&pClient->EventSrc, VBOX_SHCL_CONTEXTID_GET_EVENT(uCID)); + if (pEvent) + { + LogFlowFunc(("uCID=%RU64 -> idEvent=%RU32\n", uCID, pEvent->idEvent)); + + rc = ShClEventSignal(pEvent, pPayload); + } + /** @todo Silently skip? */ + } + break; + } + + default: + rc = VERR_NOT_FOUND; + break; + } + + if (RT_FAILURE(rc)) + { + if (pPayload) + RTMemFree(pPayload); + } + } + else + rc = VERR_NO_MEMORY; + } + } + else + rc = VERR_NO_MEMORY; + + if (RT_FAILURE(rc)) + { + if (pReply) + RTMemFree(pReply); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Transfer client (guest) handler for the Shared Clipboard host service. + * + * @returns VBox status code, or VINF_HGCM_ASYNC_EXECUTE if returning to the client will be deferred. + * @param pClient Pointer to associated client. + * @param callHandle The client's call handle of this call. + * @param u32Function Function number being called. + * @param cParms Number of function parameters supplied. + * @param aParms Array function parameters supplied. + * @param tsArrival Timestamp of arrival. + */ +int shClSvcTransferHandler(PSHCLCLIENT pClient, + VBOXHGCMCALLHANDLE callHandle, + uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM aParms[], + uint64_t tsArrival) +{ + RT_NOREF(callHandle, aParms, tsArrival); + + LogFlowFunc(("uClient=%RU32, u32Function=%RU32 (%s), cParms=%RU32, g_ExtState.pfnExtension=%p\n", + pClient->State.uClientID, u32Function, ShClGuestMsgToStr(u32Function), cParms, g_ExtState.pfnExtension)); + + /* Check if we've the right mode set. */ + if (!shClSvcTransferMsgIsAllowed(ShClSvcGetMode(), u32Function)) + { + LogFunc(("Wrong clipboard mode, denying access\n")); + return VERR_ACCESS_DENIED; + } + + int rc = VERR_INVALID_PARAMETER; /* Play safe by default. */ + + /* + * Pre-check: For certain messages we need to make sure that a (right) transfer is present. + */ + uint64_t uCID = 0; /* Context ID */ + PSHCLTRANSFER pTransfer = NULL; + + switch (u32Function) + { + default: + { + if (!ShClTransferCtxGetTotalTransfers(&pClient->Transfers.Ctx)) + { + LogFunc(("No transfers found\n")); + rc = VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; + break; + } + + if (cParms < 1) + break; + + rc = HGCMSvcGetU64(&aParms[0], &uCID); + if (RT_FAILURE(rc)) + break; + + const SHCLTRANSFERID uTransferID = VBOX_SHCL_CONTEXTID_GET_TRANSFER(uCID); + + pTransfer = ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, uTransferID); + if (!pTransfer) + { + LogFunc(("Transfer with ID %RU16 not found\n", uTransferID)); + rc = VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; + } + break; + } + } + + if (RT_FAILURE(rc)) + return rc; + + rc = VERR_INVALID_PARAMETER; /* Play safe. */ + + switch (u32Function) + { + case VBOX_SHCL_GUEST_FN_REPLY: + { + rc = shClSvcTransferHandleReply(pClient, pTransfer, cParms, aParms); + break; + } + + case VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_READ: + { + if (cParms != VBOX_SHCL_CPARMS_ROOT_LIST_HDR_READ) + break; + + if ( ShClTransferGetSource(pTransfer) == SHCLSOURCE_LOCAL + && ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_TO_REMOTE) + { + /* Get roots if this is a local write transfer (host -> guest). */ + rc = ShClBackendTransferGetRoots(pClient->pBackend, pClient, pTransfer); + } + else + { + rc = VERR_INVALID_PARAMETER; + break; + } + + SHCLROOTLISTHDR rootListHdr; + RT_ZERO(rootListHdr); + + rootListHdr.cRoots = ShClTransferRootsCount(pTransfer); + + HGCMSvcSetU64(&aParms[0], 0 /* Context ID */); + HGCMSvcSetU32(&aParms[1], rootListHdr.fRoots); + HGCMSvcSetU32(&aParms[2], rootListHdr.cRoots); + + rc = VINF_SUCCESS; + break; + } + + case VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_WRITE: + { + SHCLROOTLISTHDR lstHdr; + rc = shClSvcTransferGetRootListHdr(cParms, aParms, &lstHdr); + if (RT_SUCCESS(rc)) + { + void *pvData = ShClTransferRootListHdrDup(&lstHdr); + uint32_t cbData = sizeof(SHCLROOTLISTHDR); + + const PSHCLEVENT pEvent = ShClEventSourceGetFromId(&pClient->EventSrc, VBOX_SHCL_CONTEXTID_GET_EVENT(uCID)); + if (pEvent) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClPayloadAlloc(pEvent->idEvent, pvData, cbData, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClEventSignal(pEvent, pPayload); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + } + else + rc = VERR_SHCLPB_EVENT_ID_NOT_FOUND; + } + break; + } + + case VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_READ: + { + if (cParms != VBOX_SHCL_CPARMS_ROOT_LIST_ENTRY_READ) + break; + + /* aParms[1] contains fInfo flags, currently unused. */ + uint32_t uIndex; + rc = HGCMSvcGetU32(&aParms[2], &uIndex); + if (RT_SUCCESS(rc)) + { + SHCLROOTLISTENTRY rootListEntry; + rc = ShClTransferRootsEntry(pTransfer, uIndex, &rootListEntry); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetPv (&aParms[3], rootListEntry.pszName, rootListEntry.cbName); + HGCMSvcSetU32(&aParms[4], rootListEntry.cbInfo); + HGCMSvcSetPv (&aParms[5], rootListEntry.pvInfo, rootListEntry.cbInfo); + } + } + break; + } + + case VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_WRITE: + { + SHCLROOTLISTENTRY lstEntry; + rc = shClSvcTransferGetRootListEntry(cParms, aParms, &lstEntry); + if (RT_SUCCESS(rc)) + { + void *pvData = ShClTransferRootListEntryDup(&lstEntry); + uint32_t cbData = sizeof(SHCLROOTLISTENTRY); + + const PSHCLEVENT pEvent = ShClEventSourceGetFromId(&pClient->EventSrc, VBOX_SHCL_CONTEXTID_GET_EVENT(uCID)); + if (pEvent) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClPayloadAlloc(pEvent->idEvent, pvData, cbData, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClEventSignal(pEvent, pPayload); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + } + else + rc = VERR_SHCLPB_EVENT_ID_NOT_FOUND; + } + break; + } + + case VBOX_SHCL_GUEST_FN_LIST_OPEN: + { + SHCLLISTOPENPARMS listOpenParms; + rc = shClSvcTransferGetListOpen(cParms, aParms, &listOpenParms); + if (RT_SUCCESS(rc)) + { + SHCLLISTHANDLE hList; + rc = ShClTransferListOpen(pTransfer, &listOpenParms, &hList); + if (RT_SUCCESS(rc)) + { + /* Return list handle. */ + HGCMSvcSetU64(&aParms[6], hList); + } + } + break; + } + + case VBOX_SHCL_GUEST_FN_LIST_CLOSE: + { + if (cParms != VBOX_SHCL_CPARMS_LIST_CLOSE) + break; + + SHCLLISTHANDLE hList; + rc = HGCMSvcGetU64(&aParms[1], &hList); + if (RT_SUCCESS(rc)) + { + rc = ShClTransferListClose(pTransfer, hList); + } + break; + } + + case VBOX_SHCL_GUEST_FN_LIST_HDR_READ: + { + if (cParms != VBOX_SHCL_CPARMS_LIST_HDR) + break; + + SHCLLISTHANDLE hList; + rc = HGCMSvcGetU64(&aParms[1], &hList); /* Get list handle. */ + if (RT_SUCCESS(rc)) + { + SHCLLISTHDR hdrList; + rc = ShClTransferListGetHeader(pTransfer, hList, &hdrList); + if (RT_SUCCESS(rc)) + rc = shClSvcTransferSetListHdr(cParms, aParms, &hdrList); + } + break; + } + + case VBOX_SHCL_GUEST_FN_LIST_HDR_WRITE: + { + SHCLLISTHDR hdrList; + rc = ShClTransferListHdrInit(&hdrList); + if (RT_SUCCESS(rc)) + { + SHCLLISTHANDLE hList; + rc = shClSvcTransferGetListHdr(cParms, aParms, &hList, &hdrList); + if (RT_SUCCESS(rc)) + { + void *pvData = ShClTransferListHdrDup(&hdrList); + uint32_t cbData = sizeof(SHCLLISTHDR); + + const PSHCLEVENT pEvent = ShClEventSourceGetFromId(&pClient->EventSrc, VBOX_SHCL_CONTEXTID_GET_EVENT(uCID)); + if (pEvent) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClPayloadAlloc(pEvent->idEvent, pvData, cbData, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClEventSignal(pEvent, pPayload); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + } + else + rc = VERR_SHCLPB_EVENT_ID_NOT_FOUND; + } + } + break; + } + + case VBOX_SHCL_GUEST_FN_LIST_ENTRY_READ: + { + if (cParms != VBOX_SHCL_CPARMS_LIST_ENTRY) + break; + + SHCLLISTHANDLE hList; + rc = HGCMSvcGetU64(&aParms[1], &hList); /* Get list handle. */ + if (RT_SUCCESS(rc)) + { + SHCLLISTENTRY entryList; + rc = ShClTransferListEntryInit(&entryList); + if (RT_SUCCESS(rc)) + { + rc = ShClTransferListRead(pTransfer, hList, &entryList); + if (RT_SUCCESS(rc)) + rc = shClSvcTransferSetListEntry(cParms, aParms, &entryList); + } + } + break; + } + + case VBOX_SHCL_GUEST_FN_LIST_ENTRY_WRITE: + { + SHCLLISTENTRY entryList; + rc = ShClTransferListEntryInit(&entryList); + if (RT_SUCCESS(rc)) + { + SHCLLISTHANDLE hList; + rc = shClSvcTransferGetListEntry(cParms, aParms, &hList, &entryList); + if (RT_SUCCESS(rc)) + { + void *pvData = ShClTransferListEntryDup(&entryList); + uint32_t cbData = sizeof(SHCLLISTENTRY); + + const PSHCLEVENT pEvent = ShClEventSourceGetFromId(&pClient->EventSrc, VBOX_SHCL_CONTEXTID_GET_EVENT(uCID)); + if (pEvent) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClPayloadAlloc(pEvent->idEvent, pvData, cbData, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClEventSignal(pEvent, pPayload); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + } + else + rc = VERR_SHCLPB_EVENT_ID_NOT_FOUND; + } + } + break; + } + + case VBOX_SHCL_GUEST_FN_OBJ_OPEN: + { + ASSERT_GUEST_STMT_BREAK(cParms == VBOX_SHCL_CPARMS_OBJ_OPEN, VERR_WRONG_PARAMETER_COUNT); + + SHCLOBJOPENCREATEPARMS openCreateParms; + RT_ZERO(openCreateParms); + + /* aParms[1] will return the object handle on success; see below. */ + rc = HGCMSvcGetStr(&aParms[2], &openCreateParms.pszPath, &openCreateParms.cbPath); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&aParms[3], &openCreateParms.fCreate); + + if (RT_SUCCESS(rc)) + { + SHCLOBJHANDLE hObj; + rc = ShClTransferObjOpen(pTransfer, &openCreateParms, &hObj); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("hObj=%RU64\n", hObj)); + + HGCMSvcSetU64(&aParms[1], hObj); + } + } + break; + } + + case VBOX_SHCL_GUEST_FN_OBJ_CLOSE: + { + if (cParms != VBOX_SHCL_CPARMS_OBJ_CLOSE) + break; + + SHCLOBJHANDLE hObj; + rc = HGCMSvcGetU64(&aParms[1], &hObj); /* Get object handle. */ + if (RT_SUCCESS(rc)) + rc = ShClTransferObjClose(pTransfer, hObj); + break; + } + + case VBOX_SHCL_GUEST_FN_OBJ_READ: + { + if (cParms != VBOX_SHCL_CPARMS_OBJ_READ) + break; + + SHCLOBJHANDLE hObj; + rc = HGCMSvcGetU64(&aParms[1], &hObj); /* Get object handle. */ + + uint32_t cbToRead = 0; + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&aParms[2], &cbToRead); + + void *pvBuf = NULL; + uint32_t cbBuf = 0; + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&aParms[3], &pvBuf, &cbBuf); + + LogFlowFunc(("hObj=%RU64, cbBuf=%RU32, cbToRead=%RU32, rc=%Rrc\n", hObj, cbBuf, cbToRead, rc)); + + if ( RT_SUCCESS(rc) + && ( !cbBuf + || !cbToRead + || cbBuf < cbToRead + ) + ) + { + rc = VERR_INVALID_PARAMETER; + } + + if (RT_SUCCESS(rc)) + { + uint32_t cbRead; + rc = ShClTransferObjRead(pTransfer, hObj, pvBuf, cbToRead, 0 /* fFlags */, &cbRead); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU32(&aParms[3], cbRead); + + /** @todo Implement checksum support. */ + } + } + break; + } + + case VBOX_SHCL_GUEST_FN_OBJ_WRITE: + { + SHCLOBJDATACHUNK dataChunk; + rc = shClSvcTransferGetObjDataChunk(cParms, aParms, &dataChunk); + if (RT_SUCCESS(rc)) + { + void *pvData = ShClTransferObjDataChunkDup(&dataChunk); + uint32_t cbData = sizeof(SHCLOBJDATACHUNK); + + const PSHCLEVENT pEvent = ShClEventSourceGetFromId(&pClient->EventSrc, VBOX_SHCL_CONTEXTID_GET_EVENT(uCID)); + if (pEvent) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClPayloadAlloc(pEvent->idEvent, pvData, cbData, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClEventSignal(pEvent, pPayload); + if (RT_FAILURE(rc)) + ShClPayloadFree(pPayload); + } + } + else + rc = VERR_SHCLPB_EVENT_ID_NOT_FOUND; + } + + break; + } + + default: + rc = VERR_NOT_IMPLEMENTED; + break; + } + + LogFlowFunc(("[Client %RU32] Returning rc=%Rrc\n", pClient->State.uClientID, rc)); + return rc; +} + +/** + * Transfer host handler for the Shared Clipboard host service. + * + * @returns VBox status code. + * @param u32Function Function number being called. + * @param cParms Number of function parameters supplied. + * @param aParms Array function parameters supplied. + */ +int shClSvcTransferHostHandler(uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM aParms[]) +{ + RT_NOREF(cParms, aParms); + + int rc = VERR_NOT_IMPLEMENTED; /* Play safe. */ + + switch (u32Function) + { + case VBOX_SHCL_HOST_FN_CANCEL: /** @todo Implement this. */ + break; + + case VBOX_SHCL_HOST_FN_ERROR: /** @todo Implement this. */ + break; + + default: + break; + + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int shClSvcTransferHostMsgHandler(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) +{ + RT_NOREF(pClient); + + int rc; + + switch (pMsg->idMsg) + { + default: + rc = VINF_SUCCESS; + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Reports a transfer status to the guest. + * + * @returns VBox status code. + * @param pClient Client that owns the transfer. + * @param pTransfer Transfer to report status for. + * @param uStatus Status to report. + * @param rcTransfer Result code to report. Optional and depending on status. + * @param ppEvent Where to return the wait event on success. Optional. + * Must be released by the caller with ShClEventRelease(). + */ +int shClSvcTransferSendStatus(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLTRANSFERSTATUS uStatus, + int rcTransfer, PSHCLEVENT *ppEvent) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + /* ppEvent is optional. */ + + PSHCLCLIENTMSG pMsgReadData = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_TRANSFER_STATUS, + VBOX_SHCL_CPARMS_TRANSFER_STATUS); + if (!pMsgReadData) + return VERR_NO_MEMORY; + + PSHCLEVENT pEvent; + int rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + HGCMSvcSetU64(&pMsgReadData->aParms[0], VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + pTransfer->State.uID, pEvent->idEvent)); + HGCMSvcSetU32(&pMsgReadData->aParms[1], pTransfer->State.enmDir); + HGCMSvcSetU32(&pMsgReadData->aParms[2], uStatus); + HGCMSvcSetU32(&pMsgReadData->aParms[3], (uint32_t)rcTransfer); /** @todo uint32_t vs. int. */ + HGCMSvcSetU32(&pMsgReadData->aParms[4], 0 /* fFlags, unused */); + + shClSvcMsgAdd(pClient, pMsgReadData, true /* fAppend */); + + rc = shClSvcClientWakeup(pClient); + if (RT_SUCCESS(rc)) + { + LogRel2(("Shared Clipboard: Reported status %s (rc=%Rrc) of transfer %RU32 to guest\n", + ShClTransferStatusToStr(uStatus), rcTransfer, pTransfer->State.uID)); + + if (ppEvent) + { + *ppEvent = pEvent; /* Takes ownership. */ + } + else /* If event is not consumed by the caller, release the event again. */ + ShClEventRelease(pEvent); + } + else + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Starts a new transfer, waiting for acknowledgement by the guest side. + * + * @note Assumes that the client's critical section is taken. + * + * @returns VBox status code. + * @param pClient Client that owns the transfer. + * @param enmDir Transfer direction to start. + * @param enmSource Transfer source to start. + * @param ppTransfer Where to return the created transfer on success. Optional. + */ +int shClSvcTransferStart(PSHCLCLIENT pClient, + SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, + PSHCLTRANSFER *ppTransfer) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + /* ppTransfer is optional. */ + + LogFlowFuncEnter(); + + PSHCLTRANSFERCTX pTxCtx = &pClient->Transfers.Ctx; + + ShClTransferCtxCleanup(pTxCtx); + + int rc; + + if (!ShClTransferCtxTransfersMaximumReached(pTxCtx)) + { + LogRel2(("Shared Clipboard: Starting %s transfer ...\n", enmDir == SHCLTRANSFERDIR_FROM_REMOTE ? "read" : "write")); + + PSHCLTRANSFER pTransfer; + rc = ShClTransferCreate(&pTransfer); + if (RT_SUCCESS(rc)) + { + SHCLTXPROVIDERCREATIONCTX creationCtx; + RT_ZERO(creationCtx); + + if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE) + { + creationCtx.Interface.pfnRootsGet = shClSvcTransferIfaceGetRoots; + + creationCtx.Interface.pfnListOpen = shClSvcTransferIfaceListOpen; + creationCtx.Interface.pfnListClose = shClSvcTransferIfaceListClose; + creationCtx.Interface.pfnListHdrRead = shClSvcTransferIfaceListHdrRead; + creationCtx.Interface.pfnListEntryRead = shClSvcTransferIfaceListEntryRead; + + creationCtx.Interface.pfnObjOpen = shClSvcTransferIfaceObjOpen; + creationCtx.Interface.pfnObjClose = shClSvcTransferIfaceObjClose; + creationCtx.Interface.pfnObjRead = shClSvcTransferIfaceObjRead; + } + else if (enmDir == SHCLTRANSFERDIR_TO_REMOTE) + { + creationCtx.Interface.pfnListHdrWrite = shClSvcTransferIfaceListHdrWrite; + creationCtx.Interface.pfnListEntryWrite = shClSvcTransferIfaceListEntryWrite; + creationCtx.Interface.pfnObjWrite = shClSvcTransferIfaceObjWrite; + } + else + AssertFailed(); + + creationCtx.enmSource = pClient->State.enmSource; + creationCtx.pvUser = pClient; + + rc = ShClTransferSetProviderIface(pTransfer, &creationCtx); + if (RT_SUCCESS(rc)) + { + rc = ShClTransferInit(pTransfer, enmDir, enmSource); + if (RT_SUCCESS(rc)) + { + SHCLTRANSFERID uTransferID = 0; + rc = ShClTransferCtxTransferRegister(pTxCtx, pTransfer, &uTransferID); + if (RT_SUCCESS(rc)) + { + rc = ShClBackendTransferCreate(pClient->pBackend, pClient, pTransfer); + if (RT_SUCCESS(rc)) + { + if (RT_SUCCESS(rc)) + rc = ShClTransferStart(pTransfer); + + if (RT_SUCCESS(rc)) + { + PSHCLEVENT pEvent; + rc = shClSvcTransferSendStatus(pClient, pTransfer, + SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS, &pEvent); + if (RT_SUCCESS(rc)) + { + LogRel2(("Shared Clipboard: Waiting for start of transfer %RU32 on guest ...\n", + pTransfer->State.uID)); + + /* Leave the client's critical section before waiting. */ + RTCritSectLeave(&pClient->CritSect); + + PSHCLEVENTPAYLOAD pPayload = NULL; + rc = ShClEventWait(pEvent, pTransfer->uTimeoutMs, &pPayload); + if (RT_SUCCESS(rc)) + { + Assert(pPayload->cbData == sizeof(SHCLREPLY)); + PSHCLREPLY pReply = (PSHCLREPLY)pPayload->pvData; + AssertPtr(pReply); + + Assert(pReply->uType == VBOX_SHCL_REPLYMSGTYPE_TRANSFER_STATUS); + + if (pReply->u.TransferStatus.uStatus == SHCLTRANSFERSTATUS_STARTED) + { + LogRel2(("Shared Clipboard: Started transfer %RU32 on guest\n", pTransfer->State.uID)); + } + else + LogRel(("Shared Clipboard: Guest reported status %s (error %Rrc) while starting transfer %RU32\n", + ShClTransferStatusToStr(pReply->u.TransferStatus.uStatus), + pReply->rc, pTransfer->State.uID)); + + rc = pReply->rc; /* Set guest rc. */ + } + else + LogRel(("Shared Clipboard: Unable to start transfer %RU32 on guest, rc=%Rrc\n", + pTransfer->State.uID, rc)); + + ShClPayloadFree(pPayload); + ShClEventRelease(pEvent); + + /* Re-enter the client's critical section again. */ + RTCritSectEnter(&pClient->CritSect); + } + } + } + } + + if (RT_FAILURE(rc)) + ShClTransferCtxTransferUnregister(pTxCtx, uTransferID); + } + } + + if (RT_FAILURE(rc)) + { + ShClBackendTransferDestroy(pClient->pBackend, pClient, pTransfer); + ShClTransferDestroy(pTransfer); + + RTMemFree(pTransfer); + pTransfer = NULL; + } + else + { + if (ppTransfer) + *ppTransfer = pTransfer; + } + } + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Starting transfer failed with %Rrc\n", rc)); + } + else + rc = VERR_SHCLPB_MAX_TRANSFERS_REACHED; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Stops (and destroys) a transfer, communicating the status to the guest side. + * + * @returns VBox status code. + * @param pClient Client that owns the transfer. + * @param pTransfer Transfer to stop. + */ +int shClSvcTransferStop(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + PSHCLEVENT pEvent; + int rc = shClSvcTransferSendStatus(pClient, pTransfer, + SHCLTRANSFERSTATUS_STOPPED, VINF_SUCCESS, &pEvent); + if (RT_SUCCESS(rc)) + { + LogRel2(("Shared Clipboard: Waiting for stop of transfer %RU32 on guest ...\n", pTransfer->State.uID)); + + rc = ShClEventWait(pEvent, pTransfer->uTimeoutMs, NULL /* ppPayload */); + if (RT_SUCCESS(rc)) + LogRel2(("Shared Clipboard: Stopped transfer %RU32 on guest\n", pTransfer->State.uID)); + + ShClEventRelease(pEvent); + } + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Unable to stop transfer %RU32 on guest, rc=%Rrc\n", + pTransfer->State.uID, rc)); + + /* Regardless of whether the guest was able to report back and/or stop the transfer, remove the transfer on the host + * so that we don't risk of having stale transfers here. */ + int rc2 = ShClTransferCtxTransferUnregister(&pClient->Transfers.Ctx, ShClTransferGetID(pTransfer)); + if (RT_SUCCESS(rc2)) + { + ShClBackendTransferDestroy(pClient->pBackend, pClient, pTransfer); + ShClTransferDestroy(pTransfer); + pTransfer = NULL; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sets the host service's (file) transfer mode. + * + * @returns VBox status code. + * @param fMode Transfer mode to set. + */ +int shClSvcTransferModeSet(uint32_t fMode) +{ + if (fMode & ~VBOX_SHCL_TRANSFER_MODE_VALID_MASK) + return VERR_INVALID_FLAGS; + + g_fTransferMode = fMode; + +#ifdef DEBUG_andy +g_fTransferMode = VBOX_SHCL_TRANSFER_MODE_ENABLED; +#endif + + LogRel2(("Shared Clipboard: File transfers are now %s\n", + g_fTransferMode != VBOX_SHCL_TRANSFER_MODE_DISABLED ? "enabled" : "disabled")); + + /* If file transfers are being disabled, make sure to also reset (destroy) all pending transfers. */ + if (g_fTransferMode == VBOX_SHCL_TRANSFER_MODE_DISABLED) + { + ClipboardClientMap::const_iterator itClient = g_mapClients.begin(); + while (itClient != g_mapClients.end()) + { + PSHCLCLIENT pClient = itClient->second; + AssertPtr(pClient); + + shClSvcClientTransfersReset(pClient); + + ++itClient; + } + } + + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h new file mode 100644 index 00000000..89968d40 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h @@ -0,0 +1,35 @@ +/* $Id: VBoxSharedClipboardSvc-transfers.h $ */ +/** @file + * Shared Clipboard Service - Internal header for transfer (list) handling. + */ + +/* + * Copyright (C) 2019-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_SharedClipboard_VBoxSharedClipboardSvc_transfers_h +#define VBOX_INCLUDED_SRC_SharedClipboard_VBoxSharedClipboardSvc_transfers_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#endif /* !VBOX_INCLUDED_SRC_SharedClipboard_VBoxSharedClipboardSvc_transfers_h */ + diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-utils.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-utils.cpp new file mode 100644 index 00000000..b008b2a2 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-utils.cpp @@ -0,0 +1,42 @@ +/* $Id: VBoxSharedClipboardSvc-utils.cpp $ */ +/** @file + * Shared Clipboard Service - Host service utility functions. + */ + +/* + * Copyright (C) 2019-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <VBox/log.h> + +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <VBox/HostServices/VBoxClipboardExt.h> + +#include <iprt/errcore.h> +#include <iprt/path.h> + +#include "VBoxSharedClipboardSvc-internal.h" + diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-win.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-win.cpp new file mode 100644 index 00000000..2a8db65d --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-win.cpp @@ -0,0 +1,890 @@ +/* $Id: VBoxSharedClipboardSvc-win.cpp $ */ +/** @file + * Shared Clipboard Service - Win32 host. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <iprt/win/windows.h> + +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <VBox/GuestHost/clipboard-helper.h> +#include <VBox/GuestHost/SharedClipboard-win.h> +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include <VBox/GuestHost/SharedClipboard-transfers.h> +#endif + +#include <iprt/alloc.h> +#include <iprt/string.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/ldr.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include <iprt/utf16.h> +#endif + +#include <process.h> +#include <iprt/win/shlobj.h> /* Needed for shell objects. */ + +#include "VBoxSharedClipboardSvc-internal.h" +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include "VBoxSharedClipboardSvc-transfers.h" +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int vboxClipboardSvcWinSyncInternal(PSHCLCONTEXT pCtx); + +struct SHCLCONTEXT +{ + /** Handle for window message handling thread. */ + RTTHREAD hThread; + /** Structure for keeping and communicating with service client. */ + PSHCLCLIENT pClient; + /** Windows-specific context data. */ + SHCLWINCTX Win; +}; + + +/** + * Copy clipboard data into the guest buffer. + * + * At first attempt, guest will provide a buffer of default size. + * Usually 1K or 4K (see platform specific Guest Additions code around + * VbglR3ClipboardReadData calls). If this buffer is not big enough + * to fit host clipboard content, this function will return VINF_BUFFER_OVERFLOW + * and provide guest with host's clipboard buffer actual size. This will be a + * signal for the guest to re-read host clipboard data providing bigger buffer + * to store it. + * + * @returns IPRT status code. + * @returns VINF_BUFFER_OVERFLOW returned when guest buffer size if not big + * enough to store host clipboard data. This is a signal to the guest + * to re-issue host clipboard read request with bigger buffer size + * (specified in @a pcbActualDst output parameter). + * @param u32Format VBox clipboard format (VBOX_SHCL_FMT_XXX) of copied data. + * @param pvSrc Pointer to host clipboard data. + * @param cbSrc Size (in bytes) of actual clipboard data to copy. + * @param pvDst Pointer to guest buffer to store clipboard data. + * @param cbDst Size (in bytes) of guest buffer. + * @param pcbActualDst Actual size (in bytes) of host clipboard data. + * Only set if guest buffer size if not big enough + * to store host clipboard content. When set, + * function returns VINF_BUFFER_OVERFLOW. + */ +static int vboxClipboardSvcWinDataGet(uint32_t u32Format, const void *pvSrc, uint32_t cbSrc, + void *pvDst, uint32_t cbDst, uint32_t *pcbActualDst) +{ + AssertPtrReturn(pvSrc, VERR_INVALID_POINTER); + AssertReturn (cbSrc, VERR_INVALID_PARAMETER); + AssertPtrReturn(pvDst, VERR_INVALID_POINTER); + AssertReturn (cbDst, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbActualDst, VERR_INVALID_POINTER); + + LogFlowFunc(("cbSrc = %d, cbDst = %d\n", cbSrc, cbDst)); + + if ( u32Format == VBOX_SHCL_FMT_HTML + && SharedClipboardWinIsCFHTML((const char *)pvSrc)) + { + /** @todo r=bird: Why the double conversion? */ + char *pszBuf = NULL; + uint32_t cbBuf = 0; + int rc = SharedClipboardWinConvertCFHTMLToMIME((const char *)pvSrc, cbSrc, &pszBuf, &cbBuf); + if (RT_SUCCESS(rc)) + { + *pcbActualDst = cbBuf; + if (cbBuf > cbDst) + { + /* Do not copy data. The dst buffer is not enough. */ + RTMemFree(pszBuf); + return VINF_BUFFER_OVERFLOW; + } + memcpy(pvDst, pszBuf, cbBuf); + RTMemFree(pszBuf); + } + else + *pcbActualDst = 0; + } + else + { + *pcbActualDst = cbSrc; /* Tell the caller how much space we need. */ + + if (cbSrc > cbDst) + return VINF_BUFFER_OVERFLOW; + + memcpy(pvDst, pvSrc, cbSrc); + } + +#ifdef LOG_ENABLED + ShClDbgDumpData(pvDst, cbSrc, u32Format); +#endif + + return VINF_SUCCESS; +} + +static int vboxClipboardSvcWinDataRead(PSHCLCONTEXT pCtx, UINT uFormat, void **ppvData, uint32_t *pcbData) +{ + SHCLFORMAT fFormat = SharedClipboardWinClipboardFormatToVBox(uFormat); + LogFlowFunc(("uFormat=%u -> uFmt=0x%x\n", uFormat, fFormat)); + + if (fFormat == VBOX_SHCL_FMT_NONE) + { + LogRel2(("Shared Clipboard: Windows format %u not supported, ignoring\n", uFormat)); + return VERR_NOT_SUPPORTED; + } + + PSHCLEVENT pEvent; + int rc = ShClSvcGuestDataRequest(pCtx->pClient, fFormat, &pEvent); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, 30 * 1000, &pPayload); + if (RT_SUCCESS(rc)) + { + *ppvData = pPayload ? pPayload->pvData : NULL; + *pcbData = pPayload ? pPayload->cbData : 0; + } + + ShClEventRelease(pEvent); + } + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Reading guest clipboard data for Windows host failed with %Rrc\n", rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static LRESULT CALLBACK vboxClipboardSvcWinWndProcMain(PSHCLCONTEXT pCtx, + HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) RT_NOTHROW_DEF +{ + AssertPtr(pCtx); + + LRESULT lresultRc = 0; + + const PSHCLWINCTX pWinCtx = &pCtx->Win; + + switch (uMsg) + { + case WM_CLIPBOARDUPDATE: + { + LogFunc(("WM_CLIPBOARDUPDATE\n")); + + int rc = RTCritSectEnter(&pWinCtx->CritSect); + if (RT_SUCCESS(rc)) + { + const HWND hWndClipboardOwner = GetClipboardOwner(); + + LogFunc(("WM_CLIPBOARDUPDATE: hWndClipboardOwnerUs=%p, hWndNewClipboardOwner=%p\n", + pWinCtx->hWndClipboardOwnerUs, hWndClipboardOwner)); + + if (pWinCtx->hWndClipboardOwnerUs != hWndClipboardOwner) + { + int rc2 = RTCritSectLeave(&pWinCtx->CritSect); + AssertRC(rc2); + + /* Clipboard was updated by another application, retrieve formats and report back. */ + rc = vboxClipboardSvcWinSyncInternal(pCtx); + } + else + { + int rc2 = RTCritSectLeave(&pWinCtx->CritSect); + AssertRC(rc2); + } + } + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: WM_CLIPBOARDUPDATE failed with %Rrc\n", rc)); + + break; + } + + case WM_CHANGECBCHAIN: + { + LogFunc(("WM_CHANGECBCHAIN\n")); + lresultRc = SharedClipboardWinHandleWMChangeCBChain(pWinCtx, hWnd, uMsg, wParam, lParam); + break; + } + + case WM_DRAWCLIPBOARD: + { + LogFunc(("WM_DRAWCLIPBOARD\n")); + + int rc = RTCritSectEnter(&pWinCtx->CritSect); + if (RT_SUCCESS(rc)) + { + const HWND hWndClipboardOwner = GetClipboardOwner(); + + LogFunc(("WM_DRAWCLIPBOARD: hWndClipboardOwnerUs=%p, hWndNewClipboardOwner=%p\n", + pWinCtx->hWndClipboardOwnerUs, hWndClipboardOwner)); + + if (pWinCtx->hWndClipboardOwnerUs != hWndClipboardOwner) + { + int rc2 = RTCritSectLeave(&pWinCtx->CritSect); + AssertRC(rc2); + + /* Clipboard was updated by another application, retrieve formats and report back. */ + rc = vboxClipboardSvcWinSyncInternal(pCtx); + } + else + { + int rc2 = RTCritSectLeave(&pWinCtx->CritSect); + AssertRC(rc2); + } + } + + lresultRc = SharedClipboardWinChainPassToNext(pWinCtx, uMsg, wParam, lParam); + break; + } + + case WM_TIMER: + { + int rc = SharedClipboardWinHandleWMTimer(pWinCtx); + AssertRC(rc); + + break; + } + + case WM_RENDERFORMAT: + { + LogFunc(("WM_RENDERFORMAT\n")); + + /* Insert the requested clipboard format data into the clipboard. */ + const UINT uFormat = (UINT)wParam; + const SHCLFORMAT fFormat = SharedClipboardWinClipboardFormatToVBox(uFormat); + LogFunc(("WM_RENDERFORMAT: uFormat=%u -> fFormat=0x%x\n", uFormat, fFormat)); + + if ( fFormat == VBOX_SHCL_FMT_NONE + || pCtx->pClient == NULL) + { + /* Unsupported clipboard format is requested. */ + LogFunc(("WM_RENDERFORMAT unsupported format requested or client is not active\n")); + SharedClipboardWinClear(); + } + else + { + void *pvData = NULL; + uint32_t cbData = 0; + int rc = vboxClipboardSvcWinDataRead(pCtx, uFormat, &pvData, &cbData); + if ( RT_SUCCESS(rc) + && pvData + && cbData) + { + /* Wrap HTML clipboard content info CF_HTML format if needed. */ + if (fFormat == VBOX_SHCL_FMT_HTML + && !SharedClipboardWinIsCFHTML((char *)pvData)) + { + char *pszWrapped = NULL; + uint32_t cbWrapped = 0; + rc = SharedClipboardWinConvertMIMEToCFHTML((char *)pvData, cbData, &pszWrapped, &cbWrapped); + if (RT_SUCCESS(rc)) + { + /* Replace buffer with wrapped data content. */ + RTMemFree(pvData); + pvData = (void *)pszWrapped; + cbData = cbWrapped; + } + else + LogRel(("Shared Clipboard: cannot convert HTML clipboard into CF_HTML format, rc=%Rrc\n", rc)); + } + + rc = SharedClipboardWinDataWrite(uFormat, pvData, cbData); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Setting clipboard data for Windows host failed with %Rrc\n", rc)); + + RTMemFree(pvData); + cbData = 0; + } + + if (RT_FAILURE(rc)) + SharedClipboardWinClear(); + } + + break; + } + + case WM_RENDERALLFORMATS: + { + LogFunc(("WM_RENDERALLFORMATS\n")); + + int rc = SharedClipboardWinHandleWMRenderAllFormats(pWinCtx, hWnd); + AssertRC(rc); + + break; + } + + case SHCL_WIN_WM_REPORT_FORMATS: + { + /* Announce available formats. Do not insert data -- will be inserted in WM_RENDERFORMAT (or via IDataObject). */ + SHCLFORMATS fFormats = (uint32_t)lParam; + LogFunc(("SHCL_WIN_WM_REPORT_FORMATS: fFormats=%#xn", fFormats)); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (fFormats & VBOX_SHCL_FMT_URI_LIST) + { + PSHCLTRANSFER pTransfer; + int rc = shClSvcTransferStart(pCtx->pClient, + SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, + &pTransfer); + if (RT_SUCCESS(rc)) + { + /* Create the IDataObject implementation the host OS needs and assign + * the newly created transfer to this object. */ + rc = SharedClipboardWinTransferCreate(&pCtx->Win, pTransfer); + + /* Note: The actual requesting + retrieving of data will be done in the IDataObject implementation + (ClipboardDataObjectImpl::GetData()). */ + } + else + LogRel(("Shared Clipboard: Initializing read transfer failed with %Rrc\n", rc)); + } + else + { +#endif + int rc = SharedClipboardWinClearAndAnnounceFormats(pWinCtx, fFormats, hWnd); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Reporting clipboard formats %#x to Windows host failed with %Rrc\n", fFormats, rc)); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + } +#endif + LogFunc(("SHCL_WIN_WM_REPORT_FORMATS: lastErr=%ld\n", GetLastError())); + break; + } + + case WM_DESTROY: + { + LogFunc(("WM_DESTROY\n")); + + int rc = SharedClipboardWinHandleWMDestroy(pWinCtx); + AssertRC(rc); + + PostQuitMessage(0); + break; + } + + default: + lresultRc = DefWindowProc(hWnd, uMsg, wParam, lParam); + break; + } + + LogFlowFunc(("LEAVE hWnd=%p, WM_ %u -> %#zx\n", hWnd, uMsg, lresultRc)); + return lresultRc; +} + +/** + * Static helper function for having a per-client proxy window instances. + */ +static LRESULT CALLBACK vboxClipboardSvcWinWndProcInstance(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) RT_NOTHROW_DEF +{ + LONG_PTR pUserData = GetWindowLongPtr(hWnd, GWLP_USERDATA); + AssertPtrReturn(pUserData, 0); + + PSHCLCONTEXT pCtx = reinterpret_cast<PSHCLCONTEXT>(pUserData); + if (pCtx) + return vboxClipboardSvcWinWndProcMain(pCtx, hWnd, uMsg, wParam, lParam); + + return 0; +} + +/** + * Static helper function for routing Windows messages to a specific + * proxy window instance. + */ +static LRESULT CALLBACK vboxClipboardSvcWinWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) RT_NOTHROW_DEF +{ + /* Note: WM_NCCREATE is not the first ever message which arrives, but + * early enough for us. */ + if (uMsg == WM_NCCREATE) + { + LogFlowFunc(("WM_NCCREATE\n")); + + LPCREATESTRUCT pCS = (LPCREATESTRUCT)lParam; + AssertPtr(pCS); + SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pCS->lpCreateParams); + SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)vboxClipboardSvcWinWndProcInstance); + + return vboxClipboardSvcWinWndProcInstance(hWnd, uMsg, wParam, lParam); + } + + /* No window associated yet. */ + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +DECLCALLBACK(int) vboxClipboardSvcWinThread(RTTHREAD hThreadSelf, void *pvUser) +{ + LogFlowFuncEnter(); + + bool fThreadSignalled = false; + + const PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pvUser; + AssertPtr(pCtx); + const PSHCLWINCTX pWinCtx = &pCtx->Win; + + HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL); + + /* Register the Window Class. */ + WNDCLASS wc; + RT_ZERO(wc); + + wc.style = CS_NOCLOSE; + wc.lpfnWndProc = vboxClipboardSvcWinWndProc; + wc.hInstance = hInstance; + wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1); + + /* Register an unique wnd class name. */ + char szWndClassName[32]; + RTStrPrintf2(szWndClassName, sizeof(szWndClassName), + "%s-%RU64", SHCL_WIN_WNDCLASS_NAME, RTThreadGetNative(hThreadSelf)); + wc.lpszClassName = szWndClassName; + + int rc; + + ATOM atomWindowClass = RegisterClass(&wc); + if (atomWindowClass == 0) + { + LogFunc(("Failed to register window class\n")); + rc = VERR_NOT_SUPPORTED; + } + else + { + /* Create a window and make it a clipboard viewer. */ + pWinCtx->hWnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST, + szWndClassName, szWndClassName, + WS_POPUPWINDOW, + -200, -200, 100, 100, NULL, NULL, hInstance, pCtx /* lpParam */); + if (pWinCtx->hWnd == NULL) + { + LogFunc(("Failed to create window\n")); + rc = VERR_NOT_SUPPORTED; + } + else + { + SetWindowPos(pWinCtx->hWnd, HWND_TOPMOST, -200, -200, 0, 0, + SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE); + + rc = SharedClipboardWinChainAdd(&pCtx->Win); + if (RT_SUCCESS(rc)) + { + if (!SharedClipboardWinIsNewAPI(&pWinCtx->newAPI)) + pWinCtx->oldAPI.timerRefresh = SetTimer(pWinCtx->hWnd, 0, 10 * 1000, NULL); + } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (RT_SUCCESS(rc)) + { + HRESULT hr = OleInitialize(NULL); + if (FAILED(hr)) + { + LogRel(("Shared Clipboard: Initializing window thread OLE failed (%Rhrc) -- file transfers unavailable\n", hr)); + /* Not critical, the rest of the clipboard might work. */ + } + else + LogRel(("Shared Clipboard: Initialized window thread OLE\n")); + } +#endif + int rc2 = RTThreadUserSignal(hThreadSelf); + AssertRC(rc2); + + fThreadSignalled = true; + + MSG msg; + BOOL msgret = 0; + while ((msgret = GetMessage(&msg, NULL, 0, 0)) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + /* + * Window procedure can return error, * but this is exceptional situation that should be + * identified in testing. + */ + Assert(msgret >= 0); + LogFunc(("Message loop finished. GetMessage returned %d, message id: %d \n", msgret, msg.message)); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */ + OleUninitialize(); +#endif + } + } + + pWinCtx->hWnd = NULL; + + if (atomWindowClass != 0) + { + UnregisterClass(szWndClassName, hInstance); + atomWindowClass = 0; + } + + if (!fThreadSignalled) + { + int rc2 = RTThreadUserSignal(hThreadSelf); + AssertRC(rc2); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Synchronizes the host and the guest clipboard formats by sending all supported host clipboard + * formats to the guest. + * + * @returns VBox status code, VINF_NO_CHANGE if no synchronization was required. + * @param pCtx Clipboard context to synchronize. + */ +static int vboxClipboardSvcWinSyncInternal(PSHCLCONTEXT pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + LogFlowFuncEnter(); + + int rc; + + if (pCtx->pClient) + { + SHCLFORMATS fFormats = 0; + rc = SharedClipboardWinGetFormats(&pCtx->Win, &fFormats); + if ( RT_SUCCESS(rc) + && fFormats != VBOX_SHCL_FMT_NONE /** @todo r=bird: BUGBUG: revisit this. */ + && ShClSvcIsBackendActive()) + rc = ShClSvcHostReportFormats(pCtx->pClient, fFormats); + } + else /* If we don't have any client data (yet), bail out. */ + rc = VINF_NO_CHANGE; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/* + * Public platform dependent functions. + */ + +int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) +{ + RT_NOREF(pBackend, pTable); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + HRESULT hr = OleInitialize(NULL); + if (FAILED(hr)) + { + LogRel(("Shared Clipboard: Initializing OLE failed (%Rhrc) -- file transfers unavailable\n", hr)); + /* Not critical, the rest of the clipboard might work. */ + } + else + LogRel(("Shared Clipboard: Initialized OLE\n")); +#endif + + return VINF_SUCCESS; +} + +void ShClBackendDestroy(PSHCLBACKEND pBackend) +{ + RT_NOREF(pBackend); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */ + OleUninitialize(); +#endif +} + +int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) +{ + RT_NOREF(pBackend, fHeadless); + + LogFlowFuncEnter(); + + int rc; + + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); + if (pCtx) + { + rc = SharedClipboardWinCtxInit(&pCtx->Win); + if (RT_SUCCESS(rc)) + { + rc = RTThreadCreate(&pCtx->hThread, vboxClipboardSvcWinThread, pCtx /* pvUser */, _64K /* Stack size */, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLIP"); + if (RT_SUCCESS(rc)) + { + int rc2 = RTThreadUserWait(pCtx->hThread, 30 * 1000 /* Timeout in ms */); + AssertRC(rc2); + } + } + + pClient->State.pCtx = pCtx; + pClient->State.pCtx->pClient = pClient; + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend); + + /* Sync the host clipboard content with the client. */ + return vboxClipboardSvcWinSyncInternal(pClient->State.pCtx); +} + +int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend); + + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + LogFlowFuncEnter(); + + int rc = VINF_SUCCESS; + + PSHCLCONTEXT pCtx = pClient->State.pCtx; + if (pCtx) + { + if (pCtx->Win.hWnd) + PostMessage(pCtx->Win.hWnd, WM_DESTROY, 0 /* wParam */, 0 /* lParam */); + + if (pCtx->hThread != NIL_RTTHREAD) + { + LogFunc(("Waiting for thread to terminate ...\n")); + + /* Wait for the window thread to terminate. */ + rc = RTThreadWait(pCtx->hThread, 30 * 1000 /* Timeout in ms */, NULL); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Waiting for window thread termination failed with rc=%Rrc\n", rc)); + + pCtx->hThread = NIL_RTTHREAD; + } + + SharedClipboardWinCtxDestroy(&pCtx->Win); + + if (RT_SUCCESS(rc)) + { + RTMemFree(pCtx); + pCtx = NULL; + + pClient->State.pCtx = NULL; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + RT_NOREF(pBackend); + + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + PSHCLCONTEXT pCtx = pClient->State.pCtx; + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + LogFlowFunc(("fFormats=0x%x, hWnd=%p\n", fFormats, pCtx->Win.hWnd)); + + /* + * The guest announced formats. Forward to the window thread. + */ + PostMessage(pCtx->Win.hWnd, SHCL_WIN_WM_REPORT_FORMATS, + 0 /* wParam */, fFormats /* lParam */); + + + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} + +int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFmt, void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); + + RT_NOREF(pBackend, pCmdCtx); + + AssertPtrReturn(pClient->State.pCtx, VERR_INVALID_POINTER); + + LogFlowFunc(("uFmt=%#x\n", uFmt)); + + HANDLE hClip = NULL; + + const PSHCLWINCTX pWinCtx = &pClient->State.pCtx->Win; + + /* + * The guest wants to read data in the given format. + */ + int rc = SharedClipboardWinOpen(pWinCtx->hWnd); + if (RT_SUCCESS(rc)) + { + if (uFmt & VBOX_SHCL_FMT_BITMAP) + { + LogFunc(("CF_DIB\n")); + hClip = GetClipboardData(CF_DIB); + if (hClip != NULL) + { + LPVOID lp = GlobalLock(hClip); + if (lp != NULL) + { + rc = vboxClipboardSvcWinDataGet(VBOX_SHCL_FMT_BITMAP, lp, GlobalSize(hClip), + pvData, cbData, pcbActual); + GlobalUnlock(hClip); + } + else + { + hClip = NULL; + } + } + } + else if (uFmt & VBOX_SHCL_FMT_UNICODETEXT) + { + LogFunc(("CF_UNICODETEXT\n")); + hClip = GetClipboardData(CF_UNICODETEXT); + if (hClip != NULL) + { + LPWSTR uniString = (LPWSTR)GlobalLock(hClip); + if (uniString != NULL) + { + rc = vboxClipboardSvcWinDataGet(VBOX_SHCL_FMT_UNICODETEXT, uniString, (lstrlenW(uniString) + 1) * 2, + pvData, cbData, pcbActual); + GlobalUnlock(hClip); + } + else + { + hClip = NULL; + } + } + } + else if (uFmt & VBOX_SHCL_FMT_HTML) + { + LogFunc(("SHCL_WIN_REGFMT_HTML\n")); + UINT uRegFmt = RegisterClipboardFormat(SHCL_WIN_REGFMT_HTML); + if (uRegFmt != 0) + { + hClip = GetClipboardData(uRegFmt); + if (hClip != NULL) + { + LPVOID lp = GlobalLock(hClip); + if (lp != NULL) + { + rc = vboxClipboardSvcWinDataGet(VBOX_SHCL_FMT_HTML, lp, GlobalSize(hClip), + pvData, cbData, pcbActual); +#ifdef LOG_ENABLED + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("Raw HTML clipboard data from host:\n")); + ShClDbgDumpHtml((char *)pvData, cbData); + } +#endif + GlobalUnlock(hClip); + } + else + { + hClip = NULL; + } + } + } + } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + else if (uFmt & VBOX_SHCL_FMT_URI_LIST) + { + AssertFailed(); /** @todo */ + } +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + SharedClipboardWinClose(); + } + + if (hClip == NULL) /* Empty data is not fatal. */ + { + /* Reply with empty data. */ + vboxClipboardSvcWinDataGet(0, NULL, 0, pvData, cbData, pcbActual); + } + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Error reading host clipboard data in format %#x from Windows, rc=%Rrc\n", uFmt, rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + RT_NOREF(pBackend, pClient, pCmdCtx, uFormat, pvData, cbData); + + LogFlowFuncEnter(); + + /* Nothing to do here yet. */ + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +int ShClBackendTransferCreate(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pBackend, pClient, pTransfer); + + LogFlowFuncEnter(); + + return VINF_SUCCESS; +} + +int ShClBackendTransferDestroy(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + SharedClipboardWinTransferDestroy(&pClient->State.pCtx->Win, pTransfer); + + return VINF_SUCCESS; +} + +int ShClBackendTransferGetRoots(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + const PSHCLWINCTX pWinCtx = &pClient->State.pCtx->Win; + + int rc = SharedClipboardWinGetRoots(pWinCtx, pTransfer); + + LogFlowFuncLeaveRC(rc); + return rc; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11-stubs.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11-stubs.cpp new file mode 100644 index 00000000..13499bf3 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11-stubs.cpp @@ -0,0 +1,123 @@ +/* $Id: VBoxSharedClipboardSvc-x11-stubs.cpp $*/ +/** @file + * Shared Clipboard Service - Linux host, a stub version with no functionality for use on headless hosts. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <VBox/HostServices/VBoxClipboardSvc.h> + +#include <iprt/alloc.h> +#include <iprt/asm.h> /* For atomic operations */ +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <string.h> +#include <stdio.h> +#include <stdint.h> + +#include "VBoxSharedClipboardSvc-internal.h" + + +/* + * Initialise the host side of the shared clipboard - called by the hgcm layer. + */ +int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) +{ + RT_NOREF(pTable); + LogFlowFunc(("called, returning VINF_SUCCESS\n")); + return VINF_SUCCESS; +} + +/* + * Terminate the host side of the shared clipboard - called by the hgcm layer. + */ +void ShClBackendDestroy(PSHCLBACKEND pBackend) +{ + RT_NOREF(pBackend); + LogFlowFunc(("called, returning\n")); +} + +int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) +{ + RT_NOREF(pBackend, pClient, fHeadless); + LogFlowFunc(("called, returning VINF_SUCCESS\n")); + return VINF_SUCCESS; +} + +/* + * Synchronise the contents of the host clipboard with the guest, called by the HGCM layer + * after a save and restore of the guest. + */ +int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend, pClient); + LogFlowFunc(("called, returning VINF_SUCCESS\n")); + return VINF_SUCCESS; +} + +int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend, pClient); + return VINF_SUCCESS; +} + +/* + * The guest is taking possession of the shared clipboard. + * Called by the HGCM clipboard subsystem. + */ +int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + RT_NOREF(pBackend, pClient, fFormats); + return VINF_SUCCESS; +} + +/* + * Called by the HGCM clipboard subsystem when the guest wants to read the host clipboard. + */ +int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + RT_NOREF(pBackend, pClient, pCmdCtx, uFormat, pvData, cbData); + + /* No data available. */ + *pcbActual = 0; + + return VINF_SUCCESS; +} + +int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + RT_NOREF(pBackend, pClient, pCmdCtx, uFormat, pvData, cbData); + return VERR_NOT_IMPLEMENTED; +} + diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11.cpp new file mode 100644 index 00000000..e4eacb07 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11.cpp @@ -0,0 +1,566 @@ +/* $Id: VBoxSharedClipboardSvc-x11.cpp $ */ +/** @file + * Shared Clipboard Service - Linux host. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/env.h> +#include <iprt/mem.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/asm.h> + +#include <VBox/GuestHost/SharedClipboard.h> +#include <VBox/GuestHost/SharedClipboard-x11.h> +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <iprt/errcore.h> + +#include "VBoxSharedClipboardSvc-internal.h" + +/* Number of currently extablished connections. */ +static volatile uint32_t g_cShClConnections; + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Global context information used by the host glue for the X11 clipboard backend. + */ +struct SHCLCONTEXT +{ + /** This mutex is grabbed during any critical operations on the clipboard + * which might clash with others. */ + RTCRITSECT CritSect; + /** X11 context data. */ + SHCLX11CTX X11; + /** Pointer to the VBox host client data structure. */ + PSHCLCLIENT pClient; + /** We set this when we start shutting down as a hint not to post any new + * requests. */ + bool fShuttingDown; +}; + + +/********************************************************************************************************************************* +* Prototypes * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) shClReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser); +static DECLCALLBACK(int) shClSendDataToDestCallback(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser); +static DECLCALLBACK(int) shClRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser); + + +int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + /* Override the connection limit. */ + for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) + pTable->acMaxClients[i] = RT_MIN(VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX, pTable->acMaxClients[i]); + + RT_ZERO(pBackend->Callbacks); + /* Use internal callbacks by default. */ + pBackend->Callbacks.pfnReportFormats = shClReportFormatsCallback; + pBackend->Callbacks.pfnOnRequestDataFromSource = shClRequestDataFromSourceCallback; + pBackend->Callbacks.pfnOnSendDataToDest = shClSendDataToDestCallback; + + return VINF_SUCCESS; +} + +void ShClBackendDestroy(PSHCLBACKEND pBackend) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); +} + +void ShClBackendSetCallbacks(PSHCLBACKEND pBackend, PSHCLCALLBACKS pCallbacks) +{ +#define SET_FN_IF_NOT_NULL(a_Fn) \ + if (pCallbacks->pfn##a_Fn) \ + pBackend->Callbacks.pfn##a_Fn = pCallbacks->pfn##a_Fn; + + SET_FN_IF_NOT_NULL(ReportFormats); + SET_FN_IF_NOT_NULL(OnClipboardRead); + SET_FN_IF_NOT_NULL(OnClipboardWrite); + SET_FN_IF_NOT_NULL(OnRequestDataFromSource); + SET_FN_IF_NOT_NULL(OnSendDataToDest); + +#undef SET_FN_IF_NOT_NULL +} + +/** + * @note On the host, we assume that some other application already owns + * the clipboard and leave ownership to X11. + */ +int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) +{ + int rc; + + /* Check if maximum allowed connections count has reached. */ + if (ASMAtomicIncU32(&g_cShClConnections) > VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX) + { + ASMAtomicDecU32(&g_cShClConnections); + LogRel(("Shared Clipboard: maximum amount for client connections reached\n")); + return VERR_OUT_OF_RESOURCES; + } + + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); + if (pCtx) + { + rc = RTCritSectInit(&pCtx->CritSect); + if (RT_SUCCESS(rc)) + { + rc = ShClX11Init(&pCtx->X11, &pBackend->Callbacks, pCtx, fHeadless); + if (RT_SUCCESS(rc)) + { + pClient->State.pCtx = pCtx; + pCtx->pClient = pClient; + + rc = ShClX11ThreadStart(&pCtx->X11, true /* grab shared clipboard */); + if (RT_FAILURE(rc)) + ShClX11Destroy(&pCtx->X11); + } + + if (RT_FAILURE(rc)) + RTCritSectDelete(&pCtx->CritSect); + } + + if (RT_FAILURE(rc)) + { + pClient->State.pCtx = NULL; + RTMemFree(pCtx); + } + } + else + rc = VERR_NO_MEMORY; + + if (RT_FAILURE(rc)) + { + /* Restore active connections count. */ + ASMAtomicDecU32(&g_cShClConnections); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + /* Tell the guest we have no data in case X11 is not available. If + * there is data in the host clipboard it will automatically be sent to + * the guest when the clipboard starts up. */ + if (ShClSvcIsBackendActive()) + return ShClSvcHostReportFormats(pClient, VBOX_SHCL_FMT_NONE); + return VINF_SUCCESS; +} + +/* + * Shut down the shared clipboard service and "disconnect" the guest. + * Note! Host glue code + */ +int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + PSHCLCONTEXT pCtx = pClient->State.pCtx; + AssertPtr(pCtx); + + /* Drop the reference to the client, in case it is still there. This + * will cause any outstanding clipboard data requests from X11 to fail + * immediately. */ + pCtx->fShuttingDown = true; + + int rc = ShClX11ThreadStop(&pCtx->X11); + /** @todo handle this slightly more reasonably, or be really sure + * it won't go wrong. */ + AssertRC(rc); + + ShClX11Destroy(&pCtx->X11); + RTCritSectDelete(&pCtx->CritSect); + + RTMemFree(pCtx); + + /* Decrease active connections count. */ + ASMAtomicDecU32(&g_cShClConnections); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + RT_NOREF(pBackend); + + int rc = ShClX11ReportFormatsToX11(&pClient->State.pCtx->X11, fFormats); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** Structure describing a request for clipoard data from the guest. */ +struct CLIPREADCBREQ +{ + /** User-supplied data pointer, based on the request type. */ + void *pv; + /** The size (in bytes) of the the user-supplied pointer in pv. */ + uint32_t cb; + /** The actual size of the data written. */ + uint32_t *pcbActual; + /** The request's event ID. */ + SHCLEVENTID idEvent; +}; + +/** + * @note We always fail or complete asynchronously. + * @note On success allocates a CLIPREADCBREQ structure which must be + * freed in ClipCompleteDataRequestFromX11 when it is called back from + * the backend code. + */ +int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, + void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + RT_NOREF(pBackend); + + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); + + RT_NOREF(pCmdCtx); + + LogFlowFunc(("pClient=%p, uFormat=%#x, pv=%p, cb=%RU32, pcbActual=%p\n", + pClient, uFormat, pvData, cbData, pcbActual)); + + int rc; + + CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)RTMemAllocZ(sizeof(CLIPREADCBREQ)); + if (pReq) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + pReq->pv = pvData; + pReq->cb = cbData; + pReq->pcbActual = pcbActual; + pReq->idEvent = pEvent->idEvent; + + /* Note: ShClX11ReadDataFromX11() will consume pReq on success. */ + rc = ShClX11ReadDataFromX11(&pClient->State.pCtx->X11, uFormat, pReq); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, 30 * 1000, &pPayload); + if (RT_SUCCESS(rc)) + { + if (pPayload) + { + memcpy(pvData, pPayload->pvData, RT_MIN(cbData, pPayload->cbData)); + + *pcbActual = (uint32_t)pPayload->cbData; + + ShClPayloadFree(pPayload); + } + else /* No payload given; could happen on invalid / not-expected formats. */ + *pcbActual = 0; + } + } + + ShClEventRelease(pEvent); + } + + if (RT_FAILURE(rc)) + RTMemFree(pReq); + } + else + rc = VERR_NO_MEMORY; + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Error reading host clipboard data from X11, rc=%Rrc\n", rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + RT_NOREF(pBackend, pClient, pCmdCtx, uFormat, pvData, cbData); + + LogFlowFuncEnter(); + + /* Nothing to do here yet. */ + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +/** @copydoc SHCLCALLBACKS::pfnReportFormats */ +static DECLCALLBACK(int) shClReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser) +{ + RT_NOREF(pvUser); + + LogFlowFunc(("pCtx=%p, fFormats=%#x\n", pCtx, fFormats)); + + int rc = VINF_SUCCESS; + PSHCLCLIENT pClient = pCtx->pClient; + AssertPtr(pClient); + + rc = RTCritSectEnter(&pClient->CritSect); + if (RT_SUCCESS(rc)) + { + if (ShClSvcIsBackendActive()) + { + /** @todo r=bird: BUGBUG: Revisit this */ + if (fFormats != VBOX_SHCL_FMT_NONE) /* No formats to report? */ + { + rc = ShClSvcHostReportFormats(pCtx->pClient, fFormats); + } + } + + RTCritSectLeave(&pClient->CritSect); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** @copydoc SHCLCALLBACKS::pfnOnSendDataToDest */ +static DECLCALLBACK(int) shClSendDataToDestCallback(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvUser, VERR_INVALID_POINTER); + + PSHCLX11READDATAREQ pData = (PSHCLX11READDATAREQ)pvUser; + CLIPREADCBREQ *pReq = pData->pReq; + + LogFlowFunc(("rcCompletion=%Rrc, pReq=%p, pv=%p, cb=%RU32, idEvent=%RU32\n", + pData->rcCompletion, pReq, pv, cb, pReq->idEvent)); + + if (pReq->idEvent != NIL_SHCLEVENTID) + { + int rc2; + + PSHCLEVENTPAYLOAD pPayload = NULL; + if ( RT_SUCCESS(pData->rcCompletion) + && pv + && cb) + { + rc2 = ShClPayloadAlloc(pReq->idEvent, pv, cb, &pPayload); + AssertRC(rc2); + } + + rc2 = RTCritSectEnter(&pCtx->pClient->CritSect); + if (RT_SUCCESS(rc2)) + { + const PSHCLEVENT pEvent = ShClEventSourceGetFromId(&pCtx->pClient->EventSrc, pReq->idEvent); + if (pEvent) + rc2 = ShClEventSignal(pEvent, pPayload); + + RTCritSectLeave(&pCtx->pClient->CritSect); + + if (RT_SUCCESS(rc2)) + pPayload = NULL; + } + + if (pPayload) + ShClPayloadFree(pPayload); + } + + if (pReq) + RTMemFree(pReq); + + LogRel2(("Shared Clipboard: Reading X11 clipboard data from host completed with %Rrc\n", pData->rcCompletion)); + + return VINF_SUCCESS; +} + +/** @copydoc SHCLCALLBACKS::pfnOnRequestDataFromSource */ +static DECLCALLBACK(int) shClRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser) +{ + RT_NOREF(pvUser); + + LogFlowFunc(("pCtx=%p, uFmt=0x%x\n", pCtx, uFmt)); + + if (pCtx->fShuttingDown) + { + /* The shared clipboard is disconnecting. */ + LogRel(("Shared Clipboard: Host requested guest clipboard data after guest had disconnected\n")); + return VERR_WRONG_ORDER; + } + + PSHCLCLIENT pClient = pCtx->pClient; + AssertPtr(pClient); + + RTCritSectEnter(&pClient->CritSect); + + int rc = VINF_SUCCESS; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* + * Note: We always return a generic URI list here. + * As we don't know which Atom target format was requested by the caller, the X11 clipboard codes needs + * to decide & transform the list into the actual clipboard Atom target format the caller wanted. + */ + if (uFmt == VBOX_SHCL_FMT_URI_LIST) + { + PSHCLTRANSFER pTransfer; + rc = shClSvcTransferStart(pCtx->pClient, + SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, + &pTransfer); + if (RT_SUCCESS(rc)) + { + + } + else + LogRel(("Shared Clipboard: Initializing read transfer from guest failed with %Rrc\n", rc)); + + *ppv = NULL; + *pcb = 0; + + rc = VERR_NO_DATA; + } +#endif + + if (RT_SUCCESS(rc)) + { + /* Request data from the guest. */ + PSHCLEVENT pEvent; + rc = ShClSvcGuestDataRequest(pCtx->pClient, uFmt, &pEvent); + if (RT_SUCCESS(rc)) + { + RTCritSectLeave(&pClient->CritSect); + + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, 30 * 1000, &pPayload); + if (RT_SUCCESS(rc)) + { + if ( !pPayload + || !pPayload->cbData) + { + rc = VERR_NO_DATA; + } + else + { + *ppv = pPayload->pvData; + *pcb = pPayload->cbData; + } + } + + RTCritSectEnter(&pClient->CritSect); + + ShClEventRelease(pEvent); + } + } + + RTCritSectLeave(&pClient->CritSect); + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Requesting data in format %#x for X11 host failed with %Rrc\n", uFmt, rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + +int ShClBackendTransferCreate(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pBackend); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + return ShClHttpTransferRegister(&pClient->State.pCtx->X11.HttpCtx, pTransfer); +#else + RT_NOREF(pClient, pTransfer); +#endif + return VERR_NOT_IMPLEMENTED; +} + +int ShClBackendTransferDestroy(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pBackend); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + return ShClHttpTransferUnregister(&pClient->State.pCtx->X11.HttpCtx, pTransfer); +#else + RT_NOREF(pClient, pTransfer); +#endif + + return VINF_SUCCESS; +} + +int ShClBackendTransferGetRoots(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + PSHCLEVENT pEvent; + int rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)RTMemAllocZ(sizeof(CLIPREADCBREQ)); + if (pReq) + { + pReq->idEvent = pEvent->idEvent; + + rc = ShClX11ReadDataFromX11(&pClient->State.pCtx->X11, VBOX_SHCL_FMT_URI_LIST, pReq); + if (RT_SUCCESS(rc)) + { + /* X supplies the data asynchronously, so we need to wait for data to arrive first. */ + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, 30 * 1000, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClTransferRootsSet(pTransfer, + (char *)pPayload->pvData, pPayload->cbData + 1 /* Include termination */); + } + } + } + else + rc = VERR_NO_MEMORY; + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + + LogFlowFuncLeaveRC(rc); + return rc; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp new file mode 100644 index 00000000..571e47e8 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp @@ -0,0 +1,2834 @@ +/* $Id: VBoxSharedClipboardSvc.cpp $ */ +/** @file + * Shared Clipboard Service - Host service entry points. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_hostclip The Shared Clipboard Host Service + * + * The shared clipboard host service is the host half of the clibpoard proxying + * between the host and the guest. The guest parts live in VBoxClient, VBoxTray + * and VBoxService depending on the OS, with code shared between host and guest + * under src/VBox/GuestHost/SharedClipboard/. + * + * The service is split into a platform-independent core and platform-specific + * backends. The service defines two communication protocols - one to + * communicate with the clipboard service running on the guest, and one to + * communicate with the backend. These will be described in a very skeletal + * fashion here. + * + * r=bird: The "two communication protocols" does not seems to be factual, there + * is only one protocol, the first one mentioned. It cannot be backend + * specific, because the guest/host protocol is platform and backend agnostic in + * nature. You may call it versions, but I take a great dislike to "protocol + * versions" here, as you've just extended the existing protocol with a feature + * that allows to transfer files and directories too. See @bugref{9437#c39}. + * + * + * @section sec_hostclip_guest_proto The guest communication protocol + * + * The guest clipboard service communicates with the host service over HGCM + * (the host is a HGCM service). HGCM is connection based, so the guest side + * has to connect before anything else can be done. (Windows hosts currently + * only support one simultaneous connection.) Once it has connected, it can + * send messages to the host services, some of which will receive immediate + * replies from the host, others which will block till a reply becomes + * available. The latter is because HGCM does't allow the host to initiate + * communication, it must be guest triggered. The HGCM service is single + * threaded, so it doesn't matter if the guest tries to send lots of requests in + * parallel, the service will process them one at the time. + * + * There are currently four messages defined. The first is + * VBOX_SHCL_GUEST_FN_MSG_GET / VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, which waits + * for a message from the host. If a host message is sent while the guest is + * not waiting, it will be queued until the guest requests it. The host code + * only supports a single simultaneous GET call from one client guest. + * + * The second guest message is VBOX_SHCL_GUEST_FN_REPORT_FORMATS, which tells + * the host that the guest has new clipboard data available. The third is + * VBOX_SHCL_GUEST_FN_DATA_READ, which asks the host to send its clipboard data + * and waits until it arrives. The host supports at most one simultaneous + * VBOX_SHCL_GUEST_FN_DATA_READ call from a guest - if a second call is made + * before the first has returned, the first will be aborted. + * + * The last guest message is VBOX_SHCL_GUEST_FN_DATA_WRITE, which is used to + * send the contents of the guest clipboard to the host. This call should be + * used after the host has requested data from the guest. + * + * + * @section sec_hostclip_backend_proto The communication protocol with the + * platform-specific backend + * + * The initial protocol implementation (called protocol v0) was very simple, + * and could only handle simple data (like copied text and so on). It also + * was limited to two (2) fixed parameters at all times. + * + * Since VBox 6.1 a newer protocol (v1) has been established to also support + * file transfers. This protocol uses a (per-client) message queue instead + * (see VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT vs. VBOX_SHCL_GUEST_FN_GET_HOST_MSG). + * + * To distinguish the old (legacy) or new(er) protocol, the VBOX_SHCL_GUEST_FN_CONNECT + * message has been introduced. If an older guest does not send this message, + * an appropriate translation will be done to serve older Guest Additions (< 6.1). + * + * The protocol also support out-of-order messages by using so-called "context IDs", + * which are generated by the host. A context ID consists of a so-called "source event ID" + * and a so-called "event ID". Each HGCM client has an own, random, source event ID and + * generates non-deterministic event IDs so that the guest side does not known what + * comes next; the guest side has to reply with the same conext ID which was sent by + * the host request. + * + * Also see the protocol changelog at VBoxShClSvc.h. + * + * + * @section sec_uri_intro Transferring files + * + * Since VBox x.x.x transferring files via Shared Clipboard is supported. + * See the VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS define for supported / enabled + * platforms. This is called "Shared Clipboard transfers". + * + * Copying files / directories from guest A to guest B requires the host + * service to act as a proxy and cache, as we don't allow direct VM-to-VM + * communication. Copying from / to the host also is taken into account. + * + * At the moment a transfer is a all-or-nothing operation, e.g. it either + * completes or fails completely. There might be callbacks in the future + * to e.g. skip failing entries. + * + * Known limitations: + * + * - Support for VRDE (VRDP) is not implemented yet (see #9498). + * - Unicode support on Windows hosts / guests is not enabled (yet). + * - Symbolic links / Windows junctions are not allowed. + * - Windows alternate data streams (ADS) are not allowed. + * - No support for ACLs yet. + * - No (maybe never) support for NT4. + + * @section sec_transfer_structure Transfer handling structure + * + * All structures / classes are designed for running on both, on the guest + * (via VBoxTray / VBoxClient) or on the host (host service) to avoid code + * duplication where applicable. + * + * Per HGCM client there is a so-called "transfer context", which in turn can + * have one or mulitple so-called "Shared Clipboard transfer" objects. At the + * moment we only support on concurrent Shared Clipboard transfer per transfer + * context. It's being used for reading from a source or writing to destination, + * depening on its direction. An Shared Clipboard transfer can have optional + * callbacks which might be needed by various implementations. Also, transfers + * optionally can run in an asynchronous thread to prevent blocking the UI while + * running. + * + * @section sec_transfer_providers Transfer providers + * + * For certain implementations (for example on Windows guests / hosts, using + * IDataObject and IStream objects) a more flexible approach reqarding reading / + * writing is needed. For this so-called transfer providers abstract the way of how + * data is being read / written in the current context (host / guest), while + * the rest of the code stays the same. + * + * @section sec_transfer_protocol Transfer protocol + * + * The host service issues commands which the guest has to respond with an own + * message to. The protocol itself is designed so that it has primitives to list + * directories and open/close/read/write file system objects. + * + * Note that this is different from the DnD approach, as Shared Clipboard transfers + * need to be deeper integrated within the host / guest OS (i.e. for progress UI), + * and this might require non-monolithic / random access APIs to achieve. + * + * As there can be multiple file system objects (fs objects) selected for transfer, + * a transfer can be queried for its root entries, which then contains the top-level + * elements. Based on these elements, (a) (recursive) listing(s) can be performed + * to (partially) walk down into directories and query fs object information. The + * provider provides appropriate interface for this, even if not all implementations + * might need this mechanism. + * + * An Shared Clipboard transfer has three stages: + * - 1. Announcement: An Shared Clipboard transfer-compatible format (currently only one format available) + * has been announced, the destination side creates a transfer object, which then, + * depending on the actual implementation, can be used to tell the OS that + * there is transfer (file) data available. + * At this point this just acts as a (kind-of) promise to the OS that we + * can provide (file) data at some later point in time. + * + * - 2. Initialization: As soon as the OS requests the (file) data, mostly triggered + * by the user starting a paste operation (CTRL + V), the transfer get initialized + * on the destination side, which in turn lets the source know that a transfer + * is going to happen. + * + * - 3. Transfer: At this stage the actual transfer from source to the destination takes + * place. How the actual transfer is structurized (e.g. which files / directories + * are transferred in which order) depends on the destination implementation. This + * is necessary in order to fulfill requirements on the destination side with + * regards to ETA calculation or other dependencies. + * Both sides can abort or cancel the transfer at any time. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <VBox/log.h> +#include <VBox/vmm/vmmr3vtable.h> /* must be included before hgcmsvc.h */ + +#include <VBox/GuestHost/clipboard-helper.h> +#include <VBox/HostServices/Service.h> +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <VBox/HostServices/VBoxClipboardExt.h> + +#include <VBox/AssertGuest.h> +#include <VBox/err.h> +#include <VBox/VMMDev.h> +#include <VBox/vmm/ssm.h> + +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/rand.h> + +#include "VBoxSharedClipboardSvc-internal.h" +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include "VBoxSharedClipboardSvc-transfers.h" +#endif + +using namespace HGCM; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @name The saved state versions for the shared clipboard service. + * + * @note We set bit 31 because prior to version 0x80000002 there would be a + * structure size rather than a version number. Setting bit 31 dispells + * any possible ambiguity. + * + * @{ */ +/** The current saved state version. */ +#define VBOX_SHCL_SAVED_STATE_VER_CURRENT VBOX_SHCL_SAVED_STATE_LEGACY_CID +/** Adds the legacy context ID list. */ +#define VBOX_SHCL_SAVED_STATE_LEGACY_CID UINT32_C(0x80000005) +/** Adds the client's POD state and client state flags. + * @since 6.1 RC1 */ +#define VBOX_SHCL_SAVED_STATE_VER_6_1RC1 UINT32_C(0x80000004) +/** First attempt saving state during @bugref{9437} development. + * @since 6.1 BETA 2 */ +#define VBOX_SHCL_SAVED_STATE_VER_6_1B2 UINT32_C(0x80000003) +/** First structured version. + * @since 3.1 / r53668 */ +#define VBOX_SHCL_SAVED_STATE_VER_3_1 UINT32_C(0x80000002) +/** This was just a state memory dump, including pointers and everything. + * @note This is not supported any more. Sorry. */ +#define VBOX_SHCL_SAVED_STATE_VER_NOT_SUPP (ARCH_BITS == 64 ? UINT32_C(72) : UINT32_C(48)) +/** @} */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The backend instance data. + * Only one backend at a time is supported currently. */ +SHCLBACKEND g_ShClBackend; +PVBOXHGCMSVCHELPERS g_pHelpers; + +static RTCRITSECT g_CritSect; /** @todo r=andy Put this into some instance struct, avoid globals. */ +/** Global Shared Clipboard mode. */ +static uint32_t g_uMode = VBOX_SHCL_MODE_OFF; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Global Shared Clipboard (file) transfer mode. */ +uint32_t g_fTransferMode = VBOX_SHCL_TRANSFER_MODE_DISABLED; +#endif + +/** Is the clipboard running in headless mode? */ +static bool g_fHeadless = false; + +/** Holds the service extension state. */ +SHCLEXTSTATE g_ExtState = { 0 }; + +/** Global map of all connected clients. */ +ClipboardClientMap g_mapClients; + +/** Global list of all clients which are queued up (deferred return) and ready + * to process new commands. The key is the (unique) client ID. */ +ClipboardClientQueue g_listClientsDeferred; + +/** Host feature mask (VBOX_SHCL_HF_0_XXX) for VBOX_SHCL_GUEST_FN_REPORT_FEATURES + * and VBOX_SHCL_GUEST_FN_QUERY_FEATURES. */ +static uint64_t const g_fHostFeatures0 = VBOX_SHCL_HF_0_CONTEXT_ID +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + | VBOX_SHCL_HF_0_TRANSFERS +#endif + ; + + +/** + * Returns the current Shared Clipboard service mode. + * + * @returns Current Shared Clipboard service mode. + */ +uint32_t ShClSvcGetMode(void) +{ + return g_uMode; +} + +/** + * Returns the Shared Clipboard backend in use. + * + * @returns Pointer to backend instance. + */ +PSHCLBACKEND ShClSvcGetBackend(void) +{ + return &g_ShClBackend; +} + +/** + * Getter for headless setting. Also needed by testcase. + * + * @returns Whether service currently running in headless mode or not. + */ +bool ShClSvcGetHeadless(void) +{ + return g_fHeadless; +} + +static int shClSvcModeSet(uint32_t uMode) +{ + int rc = VERR_NOT_SUPPORTED; + + switch (uMode) + { + case VBOX_SHCL_MODE_OFF: + RT_FALL_THROUGH(); + case VBOX_SHCL_MODE_HOST_TO_GUEST: + RT_FALL_THROUGH(); + case VBOX_SHCL_MODE_GUEST_TO_HOST: + RT_FALL_THROUGH(); + case VBOX_SHCL_MODE_BIDIRECTIONAL: + { + g_uMode = uMode; + + rc = VINF_SUCCESS; + break; + } + + default: + { + g_uMode = VBOX_SHCL_MODE_OFF; + break; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Takes the global Shared Clipboard service lock. + * + * @returns \c true if locking was successful, or \c false if not. + */ +bool ShClSvcLock(void) +{ + return RT_SUCCESS(RTCritSectEnter(&g_CritSect)); +} + +/** + * Unlocks the formerly locked global Shared Clipboard service lock. + */ +void ShClSvcUnlock(void) +{ + int rc2 = RTCritSectLeave(&g_CritSect); + AssertRC(rc2); +} + +/** + * Resets a client's state message queue. + * + * @param pClient Pointer to the client data structure to reset message queue for. + * @note Caller enters pClient->CritSect. + */ +void shClSvcMsgQueueReset(PSHCLCLIENT pClient) +{ + Assert(RTCritSectIsOwner(&pClient->CritSect)); + LogFlowFuncEnter(); + + while (!RTListIsEmpty(&pClient->MsgQueue)) + { + PSHCLCLIENTMSG pMsg = RTListRemoveFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); + shClSvcMsgFree(pClient, pMsg); + } + pClient->cMsgAllocated = 0; + + while (!RTListIsEmpty(&pClient->Legacy.lstCID)) + { + PSHCLCLIENTLEGACYCID pCID = RTListRemoveFirst(&pClient->Legacy.lstCID, SHCLCLIENTLEGACYCID, Node); + RTMemFree(pCID); + } + pClient->Legacy.cCID = 0; +} + +/** + * Allocates a new clipboard message. + * + * @returns Allocated clipboard message, or NULL on failure. + * @param pClient The client which is target of this message. + * @param idMsg The message ID (VBOX_SHCL_HOST_MSG_XXX) to use + * @param cParms The number of parameters the message takes. + */ +PSHCLCLIENTMSG shClSvcMsgAlloc(PSHCLCLIENT pClient, uint32_t idMsg, uint32_t cParms) +{ + RT_NOREF(pClient); + PSHCLCLIENTMSG pMsg = (PSHCLCLIENTMSG)RTMemAllocZ(RT_UOFFSETOF_DYN(SHCLCLIENTMSG, aParms[cParms])); + if (pMsg) + { + uint32_t cAllocated = ASMAtomicIncU32(&pClient->cMsgAllocated); + if (cAllocated <= 4096) + { + RTListInit(&pMsg->ListEntry); + pMsg->cParms = cParms; + pMsg->idMsg = idMsg; + return pMsg; + } + AssertMsgFailed(("Too many messages allocated for client %u! (%u)\n", pClient->State.uClientID, cAllocated)); + ASMAtomicDecU32(&pClient->cMsgAllocated); + RTMemFree(pMsg); + } + return NULL; +} + +/** + * Frees a formerly allocated clipboard message. + * + * @param pClient The client which was the target of this message. + * @param pMsg Clipboard message to free. + */ +void shClSvcMsgFree(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) +{ + RT_NOREF(pClient); + /** @todo r=bird: Do accounting. */ + if (pMsg) + { + pMsg->idMsg = UINT32_C(0xdeadface); + RTMemFree(pMsg); + + uint32_t cAllocated = ASMAtomicDecU32(&pClient->cMsgAllocated); + Assert(cAllocated < UINT32_MAX / 2); + RT_NOREF(cAllocated); + } +} + +/** + * Sets the VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT and VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT + * return parameters. + * + * @param pMsg Message to set return parameters to. + * @param paDstParms The peek parameter vector. + * @param cDstParms The number of peek parameters (at least two). + * @remarks ASSUMES the parameters has been cleared by clientMsgPeek. + */ +static void shClSvcMsgSetPeekReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms) +{ + Assert(cDstParms >= 2); + if (paDstParms[0].type == VBOX_HGCM_SVC_PARM_32BIT) + paDstParms[0].u.uint32 = pMsg->idMsg; + else + paDstParms[0].u.uint64 = pMsg->idMsg; + paDstParms[1].u.uint32 = pMsg->cParms; + + uint32_t i = RT_MIN(cDstParms, pMsg->cParms + 2); + while (i-- > 2) + switch (pMsg->aParms[i - 2].type) + { + case VBOX_HGCM_SVC_PARM_32BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint32_t); break; + case VBOX_HGCM_SVC_PARM_64BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint64_t); break; + case VBOX_HGCM_SVC_PARM_PTR: paDstParms[i].u.uint32 = pMsg->aParms[i - 2].u.pointer.size; break; + } +} + +/** + * Sets the VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT return parameters. + * + * @returns VBox status code. + * @param pMsg The message which parameters to return to the guest. + * @param paDstParms The peek parameter vector. + * @param cDstParms The number of peek parameters should be exactly two + */ +static int shClSvcMsgSetOldWaitReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms) +{ + /* + * Assert sanity. + */ + AssertPtr(pMsg); + AssertPtrReturn(paDstParms, VERR_INVALID_POINTER); + AssertReturn(cDstParms >= 2, VERR_INVALID_PARAMETER); + + Assert(pMsg->cParms == 2); + Assert(pMsg->aParms[0].u.uint32 == pMsg->idMsg); + switch (pMsg->idMsg) + { + case VBOX_SHCL_HOST_MSG_READ_DATA: + case VBOX_SHCL_HOST_MSG_FORMATS_REPORT: + break; + default: + AssertFailed(); + } + + /* + * Set the parameters. + */ + if (pMsg->cParms > 0) + paDstParms[0] = pMsg->aParms[0]; + if (pMsg->cParms > 1) + paDstParms[1] = pMsg->aParms[1]; + return VINF_SUCCESS; +} + +/** + * Adds a new message to a client'S message queue. + * + * @param pClient Pointer to the client data structure to add new message to. + * @param pMsg Pointer to message to add. The queue then owns the pointer. + * @param fAppend Whether to append or prepend the message to the queue. + * + * @note Caller must enter critical section. + */ +void shClSvcMsgAdd(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg, bool fAppend) +{ + Assert(RTCritSectIsOwner(&pClient->CritSect)); + AssertPtr(pMsg); + + LogFlowFunc(("idMsg=%s (%RU32) cParms=%RU32 fAppend=%RTbool\n", + ShClHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms, fAppend)); + + if (fAppend) + RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry); + else + RTListPrepend(&pClient->MsgQueue, &pMsg->ListEntry); +} + + +/** + * Appends a message to the client's queue and wake it up. + * + * @returns VBox status code, though the message is consumed regardless of what + * is returned. + * @param pClient The client to queue the message on. + * @param pMsg The message to queue. Ownership is always + * transfered to the queue. + * + * @note Caller must enter critical section. + */ +int shClSvcMsgAddAndWakeupClient(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) +{ + Assert(RTCritSectIsOwner(&pClient->CritSect)); + AssertPtr(pMsg); + AssertPtr(pClient); + LogFlowFunc(("idMsg=%s (%u) cParms=%u\n", ShClHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms)); + + RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry); + return shClSvcClientWakeup(pClient); +} + +/** + * Initializes a Shared Clipboard client. + * + * @param pClient Client to initialize. + * @param uClientID HGCM client ID to assign client to. + */ +int shClSvcClientInit(PSHCLCLIENT pClient, uint32_t uClientID) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + /* Assign the client ID. */ + pClient->State.uClientID = uClientID; + + RTListInit(&pClient->MsgQueue); + pClient->cMsgAllocated = 0; + + RTListInit(&pClient->Legacy.lstCID); + pClient->Legacy.cCID = 0; + + LogFlowFunc(("[Client %RU32]\n", pClient->State.uClientID)); + + int rc = RTCritSectInit(&pClient->CritSect); + if (RT_SUCCESS(rc)) + { + /* Create the client's own event source. */ + rc = ShClEventSourceCreate(&pClient->EventSrc, 0 /* ID, ignored */); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("[Client %RU32] Using event source %RU32\n", uClientID, pClient->EventSrc.uID)); + + /* Reset the client state. */ + shclSvcClientStateReset(&pClient->State); + + /* (Re-)initialize the client state. */ + rc = shClSvcClientStateInit(&pClient->State, uClientID); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (RT_SUCCESS(rc)) + rc = ShClTransferCtxInit(&pClient->Transfers.Ctx); +#endif + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Destroys a Shared Clipboard client. + * + * @param pClient Client to destroy. + */ +void shClSvcClientDestroy(PSHCLCLIENT pClient) +{ + AssertPtrReturnVoid(pClient); + + LogFlowFunc(("[Client %RU32]\n", pClient->State.uClientID)); + + /* Make sure to send a quit message to the guest so that it can terminate gracefully. */ + RTCritSectEnter(&pClient->CritSect); + if (pClient->Pending.uType) + { + if (pClient->Pending.cParms > 1) + HGCMSvcSetU32(&pClient->Pending.paParms[0], VBOX_SHCL_HOST_MSG_QUIT); + if (pClient->Pending.cParms > 2) + HGCMSvcSetU32(&pClient->Pending.paParms[1], 0); + g_pHelpers->pfnCallComplete(pClient->Pending.hHandle, VINF_SUCCESS); + pClient->Pending.uType = 0; + pClient->Pending.cParms = 0; + pClient->Pending.hHandle = NULL; + pClient->Pending.paParms = NULL; + } + RTCritSectLeave(&pClient->CritSect); + + ShClEventSourceDestroy(&pClient->EventSrc); + + shClSvcClientStateDestroy(&pClient->State); + + PSHCLCLIENTLEGACYCID pCidIter, pCidIterNext; + RTListForEachSafe(&pClient->Legacy.lstCID, pCidIter, pCidIterNext, SHCLCLIENTLEGACYCID, Node) + { + RTMemFree(pCidIter); + } + + int rc2 = RTCritSectDelete(&pClient->CritSect); + AssertRC(rc2); + + ClipboardClientMap::iterator itClient = g_mapClients.find(pClient->State.uClientID); + if (itClient != g_mapClients.end()) + g_mapClients.erase(itClient); + else + AssertFailed(); + + LogFlowFuncLeave(); +} + +void shClSvcClientLock(PSHCLCLIENT pClient) +{ + int rc2 = RTCritSectEnter(&pClient->CritSect); + AssertRC(rc2); +} + +void shClSvcClientUnlock(PSHCLCLIENT pClient) +{ + int rc2 = RTCritSectLeave(&pClient->CritSect); + AssertRC(rc2); +} + +/** + * Resets a Shared Clipboard client. + * + * @param pClient Client to reset. + */ +void shClSvcClientReset(PSHCLCLIENT pClient) +{ + if (!pClient) + return; + + LogFlowFunc(("[Client %RU32]\n", pClient->State.uClientID)); + RTCritSectEnter(&pClient->CritSect); + + /* Reset message queue. */ + shClSvcMsgQueueReset(pClient); + + /* Reset event source. */ + ShClEventSourceReset(&pClient->EventSrc); + + /* Reset pending state. */ + RT_ZERO(pClient->Pending); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClSvcClientTransfersReset(pClient); +#endif + + shclSvcClientStateReset(&pClient->State); + + RTCritSectLeave(&pClient->CritSect); +} + +static int shClSvcClientNegogiateChunkSize(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, + uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate the request. + */ + ASSERT_GUEST_RETURN(cParms == VBOX_SHCL_CPARMS_NEGOTIATE_CHUNK_SIZE, VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uint32_t const cbClientMaxChunkSize = paParms[0].u.uint32; + ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uint32_t const cbClientChunkSize = paParms[1].u.uint32; + + uint32_t const cbHostMaxChunkSize = VBOX_SHCL_MAX_CHUNK_SIZE; /** @todo Make this configurable. */ + + /* + * Do the work. + */ + if (cbClientChunkSize == 0) /* Does the client want us to choose? */ + { + paParms[0].u.uint32 = cbHostMaxChunkSize; /* Maximum */ + paParms[1].u.uint32 = RT_MIN(pClient->State.cbChunkSize, cbHostMaxChunkSize); /* Preferred */ + + } + else /* The client told us what it supports, so update and report back. */ + { + paParms[0].u.uint32 = RT_MIN(cbClientMaxChunkSize, cbHostMaxChunkSize); /* Maximum */ + paParms[1].u.uint32 = RT_MIN(cbClientMaxChunkSize, pClient->State.cbChunkSize); /* Preferred */ + } + + int rc = g_pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + Log(("[Client %RU32] chunk size: %#RU32, max: %#RU32\n", + pClient->State.uClientID, paParms[1].u.uint32, paParms[0].u.uint32)); + } + else + LogFunc(("pfnCallComplete -> %Rrc\n", rc)); + + return VINF_HGCM_ASYNC_EXECUTE; +} + +/** + * Implements VBOX_SHCL_GUEST_FN_REPORT_FEATURES. + * + * @returns VBox status code. + * @retval VINF_HGCM_ASYNC_EXECUTE on success (we complete the message here). + * @retval VERR_ACCESS_DENIED if not master + * @retval VERR_INVALID_PARAMETER if bit 63 in the 2nd parameter isn't set. + * @retval VERR_WRONG_PARAMETER_COUNT + * + * @param pClient The client state. + * @param hCall The client's call handle. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + */ +static int shClSvcClientReportFeatures(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, + uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate the request. + */ + ASSERT_GUEST_RETURN(cParms == 2, VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); + uint64_t const fFeatures0 = paParms[0].u.uint64; + ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); + uint64_t const fFeatures1 = paParms[1].u.uint64; + ASSERT_GUEST_RETURN(fFeatures1 & VBOX_SHCL_GF_1_MUST_BE_ONE, VERR_INVALID_PARAMETER); + + /* + * Do the work. + */ + paParms[0].u.uint64 = g_fHostFeatures0; + paParms[1].u.uint64 = 0; + + int rc = g_pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + pClient->State.fGuestFeatures0 = fFeatures0; + pClient->State.fGuestFeatures1 = fFeatures1; + LogRel2(("Shared Clipboard: Guest reported the following features: %#RX64\n", + pClient->State.fGuestFeatures0)); /* Note: fFeatures1 not used yet. */ + if (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_TRANSFERS) + LogRel2(("Shared Clipboard: Guest supports file transfers\n")); + } + else + LogFunc(("pfnCallComplete -> %Rrc\n", rc)); + + return VINF_HGCM_ASYNC_EXECUTE; +} + +/** + * Implements VBOX_SHCL_GUEST_FN_QUERY_FEATURES. + * + * @returns VBox status code. + * @retval VINF_HGCM_ASYNC_EXECUTE on success (we complete the message here). + * @retval VERR_WRONG_PARAMETER_COUNT + * + * @param hCall The client's call handle. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + */ +static int shClSvcClientQueryFeatures(VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate the request. + */ + ASSERT_GUEST_RETURN(cParms == 2, VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); + ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); + ASSERT_GUEST(paParms[1].u.uint64 & RT_BIT_64(63)); + + /* + * Do the work. + */ + paParms[0].u.uint64 = g_fHostFeatures0; + paParms[1].u.uint64 = 0; + int rc = g_pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + if (RT_FAILURE(rc)) + LogFunc(("pfnCallComplete -> %Rrc\n", rc)); + + return VINF_HGCM_ASYNC_EXECUTE; +} + +/** + * Implements VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT and VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if a message was pending and is being returned. + * @retval VERR_TRY_AGAIN if no message pending and not blocking. + * @retval VERR_RESOURCE_BUSY if another read already made a waiting call. + * @retval VINF_HGCM_ASYNC_EXECUTE if message wait is pending. + * + * @param pClient The client state. + * @param hCall The client's call handle. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + * @param fWait Set if we should wait for a message, clear if to return + * immediately. + * + * @note Caller takes and leave the client's critical section. + */ +static int shClSvcClientMsgPeek(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool fWait) +{ + /* + * Validate the request. + */ + ASSERT_GUEST_MSG_RETURN(cParms >= 2, ("cParms=%u!\n", cParms), VERR_WRONG_PARAMETER_COUNT); + + uint64_t idRestoreCheck = 0; + uint32_t i = 0; + if (paParms[i].type == VBOX_HGCM_SVC_PARM_64BIT) + { + idRestoreCheck = paParms[0].u.uint64; + paParms[0].u.uint64 = 0; + i++; + } + for (; i < cParms; i++) + { + ASSERT_GUEST_MSG_RETURN(paParms[i].type == VBOX_HGCM_SVC_PARM_32BIT, ("#%u type=%u\n", i, paParms[i].type), + VERR_WRONG_PARAMETER_TYPE); + paParms[i].u.uint32 = 0; + } + + /* + * Check restore session ID. + */ + if (idRestoreCheck != 0) + { + uint64_t idRestore = g_pHelpers->pfnGetVMMDevSessionId(g_pHelpers); + if (idRestoreCheck != idRestore) + { + paParms[0].u.uint64 = idRestore; + LogFlowFunc(("[Client %RU32] VBOX_SHCL_GUEST_FN_MSG_PEEK_XXX -> VERR_VM_RESTORED (%#RX64 -> %#RX64)\n", + pClient->State.uClientID, idRestoreCheck, idRestore)); + return VERR_VM_RESTORED; + } + Assert(!g_pHelpers->pfnIsCallRestored(hCall)); + } + + /* + * Return information about the first message if one is pending in the list. + */ + PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); + if (pFirstMsg) + { + shClSvcMsgSetPeekReturn(pFirstMsg, paParms, cParms); + LogFlowFunc(("[Client %RU32] VBOX_SHCL_GUEST_FN_MSG_PEEK_XXX -> VINF_SUCCESS (idMsg=%s (%u), cParms=%u)\n", + pClient->State.uClientID, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + return VINF_SUCCESS; + } + + /* + * If we cannot wait, fail the call. + */ + if (!fWait) + { + LogFlowFunc(("[Client %RU32] GUEST_MSG_PEEK_NOWAIT -> VERR_TRY_AGAIN\n", pClient->State.uClientID)); + return VERR_TRY_AGAIN; + } + + /* + * Wait for the host to queue a message for this client. + */ + ASSERT_GUEST_MSG_RETURN(pClient->Pending.uType == 0, ("Already pending! (idClient=%RU32)\n", + pClient->State.uClientID), VERR_RESOURCE_BUSY); + pClient->Pending.hHandle = hCall; + pClient->Pending.cParms = cParms; + pClient->Pending.paParms = paParms; + pClient->Pending.uType = VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT; + LogFlowFunc(("[Client %RU32] Is now in pending mode...\n", pClient->State.uClientID)); + return VINF_HGCM_ASYNC_EXECUTE; +} + +/** + * Implements VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if a message was pending and is being returned. + * @retval VINF_HGCM_ASYNC_EXECUTE if message wait is pending. + * + * @param pClient The client state. + * @param hCall The client's call handle. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + * + * @note Caller takes and leave the client's critical section. + */ +static int shClSvcClientMsgOldGet(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate input. + */ + ASSERT_GUEST_RETURN(cParms == VBOX_SHCL_CPARMS_GET_HOST_MSG_OLD, VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* id32Msg */ + ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* f32Formats */ + + paParms[0].u.uint32 = 0; + paParms[1].u.uint32 = 0; + + /* + * If there is a message pending we can return immediately. + */ + int rc; + PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); + if (pFirstMsg) + { + LogFlowFunc(("[Client %RU32] uMsg=%s (%RU32), cParms=%RU32\n", pClient->State.uClientID, + ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + + rc = shClSvcMsgSetOldWaitReturn(pFirstMsg, paParms, cParms); + AssertPtr(g_pHelpers); + rc = g_pHelpers->pfnCallComplete(hCall, rc); + if (rc != VERR_CANCELLED) + { + RTListNodeRemove(&pFirstMsg->ListEntry); + shClSvcMsgFree(pClient, pFirstMsg); + + rc = VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */ + } + } + /* + * Otherwise we must wait. + */ + else + { + ASSERT_GUEST_MSG_RETURN(pClient->Pending.uType == 0, ("Already pending! (idClient=%RU32)\n", pClient->State.uClientID), + VERR_RESOURCE_BUSY); + + pClient->Pending.hHandle = hCall; + pClient->Pending.cParms = cParms; + pClient->Pending.paParms = paParms; + pClient->Pending.uType = VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT; + + rc = VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */ + + LogFlowFunc(("[Client %RU32] Is now in pending mode...\n", pClient->State.uClientID)); + } + + LogFlowFunc(("[Client %RU32] rc=%Rrc\n", pClient->State.uClientID, rc)); + return rc; +} + +/** + * Implements VBOX_SHCL_GUEST_FN_MSG_GET. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if message retrieved and removed from the pending queue. + * @retval VERR_TRY_AGAIN if no message pending. + * @retval VERR_BUFFER_OVERFLOW if a parmeter buffer is too small. The buffer + * size was updated to reflect the required size, though this isn't yet + * forwarded to the guest. (The guest is better of using peek with + * parameter count + 2 parameters to get the sizes.) + * @retval VERR_MISMATCH if the incoming message ID does not match the pending. + * @retval VINF_HGCM_ASYNC_EXECUTE if message was completed already. + * + * @param pClient The client state. + * @param hCall The client's call handle. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + * + * @note Called from within pClient->CritSect. + */ +static int shClSvcClientMsgGet(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate the request. + */ + uint32_t const idMsgExpected = cParms > 0 && paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT ? paParms[0].u.uint32 + : cParms > 0 && paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT ? paParms[0].u.uint64 + : UINT32_MAX; + + /* + * Return information about the first message if one is pending in the list. + */ + PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); + if (pFirstMsg) + { + LogFlowFunc(("First message is: %s (%u), cParms=%RU32\n", ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + + ASSERT_GUEST_MSG_RETURN(pFirstMsg->idMsg == idMsgExpected || idMsgExpected == UINT32_MAX, + ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n", + pFirstMsg->idMsg, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->cParms, + idMsgExpected, ShClHostMsgToStr(idMsgExpected), cParms), + VERR_MISMATCH); + ASSERT_GUEST_MSG_RETURN(pFirstMsg->cParms == cParms, + ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n", + pFirstMsg->idMsg, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->cParms, + idMsgExpected, ShClHostMsgToStr(idMsgExpected), cParms), + VERR_WRONG_PARAMETER_COUNT); + + /* Check the parameter types. */ + for (uint32_t i = 0; i < cParms; i++) + ASSERT_GUEST_MSG_RETURN(pFirstMsg->aParms[i].type == paParms[i].type, + ("param #%u: type %u, caller expected %u (idMsg=%u %s)\n", i, pFirstMsg->aParms[i].type, + paParms[i].type, pFirstMsg->idMsg, ShClHostMsgToStr(pFirstMsg->idMsg)), + VERR_WRONG_PARAMETER_TYPE); + /* + * Copy out the parameters. + * + * No assertions on buffer overflows, and keep going till the end so we can + * communicate all the required buffer sizes. + */ + int rc = VINF_SUCCESS; + for (uint32_t i = 0; i < cParms; i++) + switch (pFirstMsg->aParms[i].type) + { + case VBOX_HGCM_SVC_PARM_32BIT: + paParms[i].u.uint32 = pFirstMsg->aParms[i].u.uint32; + break; + + case VBOX_HGCM_SVC_PARM_64BIT: + paParms[i].u.uint64 = pFirstMsg->aParms[i].u.uint64; + break; + + case VBOX_HGCM_SVC_PARM_PTR: + { + uint32_t const cbSrc = pFirstMsg->aParms[i].u.pointer.size; + uint32_t const cbDst = paParms[i].u.pointer.size; + paParms[i].u.pointer.size = cbSrc; /** @todo Check if this is safe in other layers... + * Update: Safe, yes, but VMMDevHGCM doesn't pass it along. */ + if (cbSrc <= cbDst) + memcpy(paParms[i].u.pointer.addr, pFirstMsg->aParms[i].u.pointer.addr, cbSrc); + else + { + AssertMsgFailed(("#%u: cbSrc=%RU32 is bigger than cbDst=%RU32\n", i, cbSrc, cbDst)); + rc = VERR_BUFFER_OVERFLOW; + } + break; + } + + default: + AssertMsgFailed(("#%u: %u\n", i, pFirstMsg->aParms[i].type)); + rc = VERR_INTERNAL_ERROR; + break; + } + if (RT_SUCCESS(rc)) + { + /* + * Complete the message and remove the pending message unless the + * guest raced us and cancelled this call in the meantime. + */ + AssertPtr(g_pHelpers); + rc = g_pHelpers->pfnCallComplete(hCall, rc); + + LogFlowFunc(("[Client %RU32] pfnCallComplete -> %Rrc\n", pClient->State.uClientID, rc)); + + if (rc != VERR_CANCELLED) + { + RTListNodeRemove(&pFirstMsg->ListEntry); + shClSvcMsgFree(pClient, pFirstMsg); + } + + return VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */ + } + + LogFlowFunc(("[Client %RU32] Returning %Rrc\n", pClient->State.uClientID, rc)); + return rc; + } + + paParms[0].u.uint32 = 0; + paParms[1].u.uint32 = 0; + LogFlowFunc(("[Client %RU32] -> VERR_TRY_AGAIN\n", pClient->State.uClientID)); + return VERR_TRY_AGAIN; +} + +/** + * Implements VBOX_SHCL_GUEST_FN_MSG_GET. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if message retrieved and removed from the pending queue. + * @retval VERR_TRY_AGAIN if no message pending. + * @retval VERR_MISMATCH if the incoming message ID does not match the pending. + * @retval VINF_HGCM_ASYNC_EXECUTE if message was completed already. + * + * @param pClient The client state. + * @param cParms Number of parameters. + * + * @note Called from within pClient->CritSect. + */ +static int shClSvcClientMsgCancel(PSHCLCLIENT pClient, uint32_t cParms) +{ + /* + * Validate the request. + */ + ASSERT_GUEST_MSG_RETURN(cParms == 0, ("cParms=%u!\n", cParms), VERR_WRONG_PARAMETER_COUNT); + + /* + * Execute. + */ + if (pClient->Pending.uType != 0) + { + LogFlowFunc(("[Client %RU32] Cancelling waiting thread, isPending=%d, pendingNumParms=%RU32, m_idSession=%x\n", + pClient->State.uClientID, pClient->Pending.uType, pClient->Pending.cParms, pClient->State.uSessionID)); + + /* + * The PEEK call is simple: At least two parameters, all set to zero before sleeping. + */ + int rcComplete; + if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT) + { + Assert(pClient->Pending.cParms >= 2); + if (pClient->Pending.paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT) + HGCMSvcSetU64(&pClient->Pending.paParms[0], VBOX_SHCL_HOST_MSG_CANCELED); + else + HGCMSvcSetU32(&pClient->Pending.paParms[0], VBOX_SHCL_HOST_MSG_CANCELED); + rcComplete = VINF_TRY_AGAIN; + } + /* + * The MSG_OLD call is complicated, though we're + * generally here to wake up someone who is peeking and have two parameters. + * If there aren't two parameters, fail the call. + */ + else + { + Assert(pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT); + if (pClient->Pending.cParms > 0) + HGCMSvcSetU32(&pClient->Pending.paParms[0], VBOX_SHCL_HOST_MSG_CANCELED); + if (pClient->Pending.cParms > 1) + HGCMSvcSetU32(&pClient->Pending.paParms[1], 0); + rcComplete = pClient->Pending.cParms == 2 ? VINF_SUCCESS : VERR_TRY_AGAIN; + } + + g_pHelpers->pfnCallComplete(pClient->Pending.hHandle, rcComplete); + + pClient->Pending.hHandle = NULL; + pClient->Pending.paParms = NULL; + pClient->Pending.cParms = 0; + pClient->Pending.uType = 0; + return VINF_SUCCESS; + } + return VWRN_NOT_FOUND; +} + + +/** + * Wakes up a pending client (i.e. waiting for new messages). + * + * @returns VBox status code. + * @retval VINF_NO_CHANGE if the client is not in pending mode. + * + * @param pClient Client to wake up. + * @note Caller must enter pClient->CritSect. + */ +int shClSvcClientWakeup(PSHCLCLIENT pClient) +{ + Assert(RTCritSectIsOwner(&pClient->CritSect)); + int rc = VINF_NO_CHANGE; + + if (pClient->Pending.uType != 0) + { + LogFunc(("[Client %RU32] Waking up ...\n", pClient->State.uClientID)); + + PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); + AssertReturn(pFirstMsg, VERR_INTERNAL_ERROR); + + LogFunc(("[Client %RU32] Current host message is %s (%RU32), cParms=%RU32\n", + pClient->State.uClientID, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + + if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT) + shClSvcMsgSetPeekReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms); + else if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT) /* Legacy, Guest Additions < 6.1. */ + shClSvcMsgSetOldWaitReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms); + else + AssertMsgFailedReturn(("pClient->Pending.uType=%u\n", pClient->Pending.uType), VERR_INTERNAL_ERROR_3); + + rc = g_pHelpers->pfnCallComplete(pClient->Pending.hHandle, VINF_SUCCESS); + + if ( rc != VERR_CANCELLED + && pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT) + { + RTListNodeRemove(&pFirstMsg->ListEntry); + shClSvcMsgFree(pClient, pFirstMsg); + } + + pClient->Pending.hHandle = NULL; + pClient->Pending.paParms = NULL; + pClient->Pending.cParms = 0; + pClient->Pending.uType = 0; + } + else + LogFunc(("[Client %RU32] Not in pending state, skipping wakeup\n", pClient->State.uClientID)); + + return rc; +} + +/** + * Requests to read clipboard data from the guest. + * + * @returns VBox status code. + * @param pClient Client to request to read data form. + * @param fFormats The formats being requested, OR'ed together (VBOX_SHCL_FMT_XXX). + * @param ppEvent Where to return the event for waiting for new data on success. Optional. + * Must be released by the caller with ShClEventRelease(). + */ +int ShClSvcGuestDataRequest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, PSHCLEVENT *ppEvent) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + LogFlowFunc(("fFormats=%#x\n", fFormats)); + + int rc = VERR_NOT_SUPPORTED; + + /* Generate a separate message for every (valid) format we support. */ + while (fFormats) + { + /* Pick the next format to get from the mask: */ + /** @todo Make format reporting precedence configurable? */ + SHCLFORMAT fFormat; + if (fFormats & VBOX_SHCL_FMT_UNICODETEXT) + fFormat = VBOX_SHCL_FMT_UNICODETEXT; + else if (fFormats & VBOX_SHCL_FMT_BITMAP) + fFormat = VBOX_SHCL_FMT_BITMAP; + else if (fFormats & VBOX_SHCL_FMT_HTML) + fFormat = VBOX_SHCL_FMT_HTML; + else + AssertMsgFailedBreak(("%#x\n", fFormats)); + + /* Remove it from the mask. */ + fFormats &= ~fFormat; + +#ifdef LOG_ENABLED + char *pszFmt = ShClFormatsToStrA(fFormat); + AssertPtrReturn(pszFmt, VERR_NO_MEMORY); + LogRel2(("Shared Clipboard: Requesting guest clipboard data in format '%s'\n", pszFmt)); + RTStrFree(pszFmt); +#endif + /* + * Allocate messages, one for each format. + */ + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, + pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID + ? VBOX_SHCL_HOST_MSG_READ_DATA_CID : VBOX_SHCL_HOST_MSG_READ_DATA, + 2); + if (pMsg) + { + /* + * Enter the critical section and generate an event. + */ + RTCritSectEnter(&pClient->CritSect); + + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("fFormats=%#x -> fFormat=%#x, idEvent=%#x\n", fFormats, fFormat, pEvent->idEvent)); + + const uint64_t uCID = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pClient->EventSrc.uID, pEvent->idEvent); + + rc = VINF_SUCCESS; + + /* Save the context ID in our legacy cruft if we have to deal with old(er) Guest Additions (< 6.1). */ + if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) + { + AssertStmt(pClient->Legacy.cCID < 4096, rc = VERR_TOO_MUCH_DATA); + if (RT_SUCCESS(rc)) + { + PSHCLCLIENTLEGACYCID pCID = (PSHCLCLIENTLEGACYCID)RTMemAlloc(sizeof(SHCLCLIENTLEGACYCID)); + if (pCID) + { + pCID->uCID = uCID; + pCID->enmType = 0; /* Not used yet. */ + pCID->uFormat = fFormat; + RTListAppend(&pClient->Legacy.lstCID, &pCID->Node); + pClient->Legacy.cCID++; + } + else + rc = VERR_NO_MEMORY; + } + } + + if (RT_SUCCESS(rc)) + { + /* + * Format the message. + */ + if (pMsg->idMsg == VBOX_SHCL_HOST_MSG_READ_DATA_CID) + HGCMSvcSetU64(&pMsg->aParms[0], uCID); + else + HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_READ_DATA); + HGCMSvcSetU32(&pMsg->aParms[1], fFormat); + + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + + /* Return event handle to the caller if requested. */ + if (ppEvent) + { + *ppEvent = pEvent; + } + + shClSvcClientWakeup(pClient); + } + + /* Remove event from list if caller did not request event handle or in case + * of failure (in this case caller should not release event). */ + if ( RT_FAILURE(rc) + || !ppEvent) + { + ShClEventRelease(pEvent); + } + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + + RTCritSectLeave(&pClient->CritSect); + + if (RT_FAILURE(rc)) + shClSvcMsgFree(pClient, pMsg); + } + else + rc = VERR_NO_MEMORY; + + if (RT_FAILURE(rc)) + break; + } + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Requesting data in formats %#x from guest failed with %Rrc\n", fFormats, rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Signals the host that clipboard data from the guest has been received. + * + * @returns VBox status code. Returns VERR_NOT_FOUND when related event ID was not found. + * @param pClient Client the guest clipboard data was received from. + * @param pCmdCtx Client command context. + * @param uFormat Clipboard format of data received. + * @param pvData Pointer to clipboard data received. This can be + * NULL if @a cbData is zero. + * @param cbData Size (in bytes) of clipboard data received. + * This can be zero. + */ +int ShClSvcGuestDataSignal(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + LogFlowFuncEnter(); + RT_NOREF(uFormat); + + /* + * Validate input. + */ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + if (cbData > 0) + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + + const SHCLEVENTID idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(pCmdCtx->uContextID); + AssertMsgReturn(idEvent != NIL_SHCLEVENTID, ("NIL event in context ID %#RX64\n", pCmdCtx->uContextID), VERR_WRONG_ORDER); + + PSHCLEVENT pEvent = ShClEventSourceGetFromId(&pClient->EventSrc, idEvent); + AssertMsgReturn(pEvent != NULL, ("Event %#x not found\n", idEvent), VERR_NOT_FOUND); + + /* + * Make a copy of the data so we can attach it to the signal. + * + * Note! We still signal the waiter should we run out of memory, + * because otherwise it will be stuck waiting. + */ + int rc = VINF_SUCCESS; + PSHCLEVENTPAYLOAD pPayload = NULL; + if (cbData > 0) + rc = ShClPayloadAlloc(idEvent, pvData, cbData, &pPayload); + + /* + * Signal the event. + */ + int rc2 = ShClEventSignal(pEvent, pPayload); + if (RT_FAILURE(rc2)) + { + rc = rc2; + ShClPayloadFree(pPayload); + LogRel(("Shared Clipboard: Signalling of guest clipboard data to the host failed: %Rrc\n", rc)); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Reports available VBox clipboard formats to the guest. + * + * @note Host backend callers must check if it's active (use + * ShClSvcIsBackendActive) before calling to prevent mixing up the + * VRDE clipboard. + * + * @returns VBox status code. + * @param pClient Client to report clipboard formats to. + * @param fFormats The formats to report (VBOX_SHCL_FMT_XXX), zero + * is okay (empty the clipboard). + */ +int ShClSvcHostReportFormats(PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + /* + * Check if the service mode allows this operation and whether the guest is + * supposed to be reading from the host. Otherwise, silently ignore reporting + * formats and return VINF_SUCCESS in order to do not trigger client + * termination in svcConnect(). + */ + uint32_t uMode = ShClSvcGetMode(); + if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL + || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) + { /* likely */ } + else + return VINF_SUCCESS; + + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + LogFlowFunc(("fFormats=%#x\n", fFormats)); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* + * If transfer mode is set to disabled, don't report the URI list format to the guest. + */ + if (!(g_fTransferMode & VBOX_SHCL_TRANSFER_MODE_ENABLED)) + { + fFormats &= ~VBOX_SHCL_FMT_URI_LIST; + LogRel2(("Shared Clipboard: File transfers are disabled, skipping reporting those to the guest\n")); + } +#endif + +#ifdef LOG_ENABLED + char *pszFmts = ShClFormatsToStrA(fFormats); + AssertPtrReturn(pszFmts, VERR_NO_MEMORY); + LogRel2(("Shared Clipboard: Reporting formats '%s' to guest\n", pszFmts)); + RTStrFree(pszFmts); +#endif + + /* + * Allocate a message, populate parameters and post it to the client. + */ + int rc; + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_FORMATS_REPORT, 2); + if (pMsg) + { + HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + HGCMSvcSetU32(&pMsg->aParms[1], fFormats); + + RTCritSectEnter(&pClient->CritSect); + shClSvcMsgAddAndWakeupClient(pClient, pMsg); + RTCritSectLeave(&pClient->CritSect); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* If we announce an URI list, create a transfer locally and also tell the guest to create + * a transfer on the guest side. */ + if (fFormats & VBOX_SHCL_FMT_URI_LIST) + { + rc = shClSvcTransferStart(pClient, SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, + NULL /* pTransfer */); + if (RT_SUCCESS(rc)) + rc = shClSvcSetSource(pClient, SHCLSOURCE_LOCAL); + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Initializing host write transfer failed with %Rrc\n", rc)); + } + else +#endif + { + rc = VINF_SUCCESS; + } + } + else + rc = VERR_NO_MEMORY; + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Reporting formats %#x to guest failed with %Rrc\n", fFormats, rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Handles the VBOX_SHCL_GUEST_FN_REPORT_FORMATS message from the guest. + */ +static int shClSvcClientReportFormats(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Check if the service mode allows this operation and whether the guest is + * supposed to be reading from the host. + */ + uint32_t uMode = ShClSvcGetMode(); + if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL + || uMode == VBOX_SHCL_MODE_GUEST_TO_HOST) + { /* likely */ } + else + return VERR_ACCESS_DENIED; + + /* + * Digest parameters. + */ + ASSERT_GUEST_RETURN( cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS + || ( cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS_61B + && (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)), + VERR_WRONG_PARAMETER_COUNT); + + uintptr_t iParm = 0; + if (cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS_61B) + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); + /* no defined value, so just ignore it */ + iParm++; + } + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uint32_t const fFormats = paParms[iParm].u.uint32; + iParm++; + if (cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS_61B) + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + ASSERT_GUEST_RETURN(paParms[iParm].u.uint32 == 0, VERR_INVALID_FLAGS); + iParm++; + } + Assert(iParm == cParms); + + /* + * Report the formats. + * + * We ignore empty reports if the guest isn't the clipboard owner, this + * prevents a freshly booted guest with an empty clibpoard from clearing + * the host clipboard on startup. Likewise, when a guest shutdown it will + * typically issue an empty report in case it's the owner, we don't want + * that to clear host content either. + */ + int rc; + if (!fFormats && pClient->State.enmSource != SHCLSOURCE_REMOTE) + rc = VINF_SUCCESS; + else + { + rc = shClSvcSetSource(pClient, SHCLSOURCE_REMOTE); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectEnter(&g_CritSect); + if (RT_SUCCESS(rc)) + { + if (g_ExtState.pfnExtension) + { + SHCLEXTPARMS parms; + RT_ZERO(parms); + parms.uFormat = fFormats; + + g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE, &parms, sizeof(parms)); + } + else + { +#ifdef LOG_ENABLED + char *pszFmts = ShClFormatsToStrA(fFormats); + if (pszFmts) + { + LogRel2(("Shared Clipboard: Guest reported formats '%s' to host\n", pszFmts)); + RTStrFree(pszFmts); + } +#endif + rc = ShClBackendReportFormats(&g_ShClBackend, pClient, fFormats); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Reporting guest clipboard formats to the host failed with %Rrc\n", rc)); + } + + RTCritSectLeave(&g_CritSect); + } + else + LogRel2(("Shared Clipboard: Unable to take internal lock while receiving guest clipboard announcement: %Rrc\n", rc)); + } + } + + return rc; +} + +/** + * Called when the guest wants to read host clipboard data. + * Handles the VBOX_SHCL_GUEST_FN_DATA_READ message. + * + * @returns VBox status code. + * @retval VINF_BUFFER_OVERFLOW if the guest supplied a smaller buffer than needed in order to read the host clipboard data. + * @param pClient Client that wants to read host clipboard data. + * @param cParms Number of HGCM parameters supplied in \a paParms. + * @param paParms Array of HGCM parameters. + */ +static int shClSvcClientReadData(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + LogFlowFuncEnter(); + + /* + * Check if the service mode allows this operation and whether the guest is + * supposed to be reading from the host. + */ + uint32_t uMode = ShClSvcGetMode(); + if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL + || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) + { /* likely */ } + else + return VERR_ACCESS_DENIED; + + /* + * Digest parameters. + * + * We are dragging some legacy here from the 6.1 dev cycle, a 5 parameter + * variant which prepends a 64-bit context ID (RAZ as meaning not defined), + * a 32-bit flag (MBZ, no defined meaning) and switches the last two parameters. + */ + ASSERT_GUEST_RETURN( cParms == VBOX_SHCL_CPARMS_DATA_READ + || ( cParms == VBOX_SHCL_CPARMS_DATA_READ_61B + && (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)), + VERR_WRONG_PARAMETER_COUNT); + + uintptr_t iParm = 0; + SHCLCLIENTCMDCTX cmdCtx; + RT_ZERO(cmdCtx); + if (cParms == VBOX_SHCL_CPARMS_DATA_READ_61B) + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); + /* This has no defined meaning and was never used, however the guest passed stuff, so ignore it and leave idContext=0. */ + iParm++; + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + ASSERT_GUEST_RETURN(paParms[iParm].u.uint32 == 0, VERR_INVALID_FLAGS); + iParm++; + } + + SHCLFORMAT uFormat = VBOX_SHCL_FMT_NONE; + uint32_t cbData = 0; + void *pvData = NULL; + + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uFormat = paParms[iParm].u.uint32; + iParm++; + if (cParms != VBOX_SHCL_CPARMS_DATA_READ_61B) + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Data buffer */ + pvData = paParms[iParm].u.pointer.addr; + cbData = paParms[iParm].u.pointer.size; + iParm++; + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /*cbDataReturned*/ + iParm++; + } + else + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /*cbDataReturned*/ + iParm++; + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Data buffer */ + pvData = paParms[iParm].u.pointer.addr; + cbData = paParms[iParm].u.pointer.size; + iParm++; + } + Assert(iParm == cParms); + + /* + * For some reason we need to do this (makes absolutely no sense to bird). + */ + /** @todo r=bird: I really don't get why you need the State.POD.uFormat + * member. I'm sure there is a reason. Incomplete code? */ + if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) + { + if (pClient->State.POD.uFormat == VBOX_SHCL_FMT_NONE) + pClient->State.POD.uFormat = uFormat; + } + +#ifdef LOG_ENABLED + char *pszFmt = ShClFormatsToStrA(uFormat); + AssertPtrReturn(pszFmt, VERR_NO_MEMORY); + LogRel2(("Shared Clipboard: Guest wants to read %RU32 bytes host clipboard data in format '%s'\n", cbData, pszFmt)); + RTStrFree(pszFmt); +#endif + + /* + * Do the reading. + */ + uint32_t cbActual = 0; + + int rc = RTCritSectEnter(&g_CritSect); + AssertRCReturn(rc, rc); + + /* If there is a service extension active, try reading data from it first. */ + if (g_ExtState.pfnExtension) + { + SHCLEXTPARMS parms; + RT_ZERO(parms); + + parms.uFormat = uFormat; + parms.u.pvData = pvData; + parms.cbData = cbData; + + g_ExtState.fReadingData = true; + + /* Read clipboard data from the extension. */ + rc = g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_DATA_READ, &parms, sizeof(parms)); + + LogRel2(("Shared Clipboard: Read extension clipboard data (fDelayedAnnouncement=%RTbool, fDelayedFormats=%#x, max %RU32 bytes), got %RU32 bytes: rc=%Rrc\n", + g_ExtState.fDelayedAnnouncement, g_ExtState.fDelayedFormats, cbData, parms.cbData, rc)); + + /* Did the extension send the clipboard formats yet? + * Otherwise, do this now. */ + if (g_ExtState.fDelayedAnnouncement) + { + int rc2 = ShClSvcHostReportFormats(pClient, g_ExtState.fDelayedFormats); + AssertRC(rc2); + + g_ExtState.fDelayedAnnouncement = false; + g_ExtState.fDelayedFormats = 0; + } + + g_ExtState.fReadingData = false; + + if (RT_SUCCESS(rc)) + cbActual = parms.cbData; + } + else + { + rc = ShClBackendReadData(&g_ShClBackend, pClient, &cmdCtx, uFormat, pvData, cbData, &cbActual); + if (RT_SUCCESS(rc)) + LogRel2(("Shared Clipboard: Read host clipboard data (max %RU32 bytes), got %RU32 bytes\n", cbData, cbActual)); + else + LogRel(("Shared Clipboard: Reading host clipboard data failed with %Rrc\n", rc)); + } + + if (RT_SUCCESS(rc)) + { + /* Return the actual size required to fullfil the request. */ + if (cParms != VBOX_SHCL_CPARMS_DATA_READ_61B) + HGCMSvcSetU32(&paParms[2], cbActual); + else + HGCMSvcSetU32(&paParms[3], cbActual); + + /* If the data to return exceeds the buffer the guest supplies, tell it (and let it try again). */ + if (cbActual >= cbData) + rc = VINF_BUFFER_OVERFLOW; + } + + RTCritSectLeave(&g_CritSect); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Called when the guest writes clipboard data to the host. + * Handles the VBOX_SHCL_GUEST_FN_DATA_WRITE message. + * + * @returns VBox status code. + * @param pClient Client that wants to read host clipboard data. + * @param cParms Number of HGCM parameters supplied in \a paParms. + * @param paParms Array of HGCM parameters. + */ +int shClSvcClientWriteData(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + LogFlowFuncEnter(); + + /* + * Check if the service mode allows this operation and whether the guest is + * supposed to be reading from the host. + */ + uint32_t uMode = ShClSvcGetMode(); + if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL + || uMode == VBOX_SHCL_MODE_GUEST_TO_HOST) + { /* likely */ } + else + return VERR_ACCESS_DENIED; + + const bool fReportsContextID = RT_BOOL(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID); + + /* + * Digest parameters. + * + * There are 3 different format here, formatunately no parameters have been + * switch around so it's plain sailing compared to the DATA_READ message. + */ + ASSERT_GUEST_RETURN(fReportsContextID + ? cParms == VBOX_SHCL_CPARMS_DATA_WRITE || cParms == VBOX_SHCL_CPARMS_DATA_WRITE_61B + : cParms == VBOX_SHCL_CPARMS_DATA_WRITE_OLD, + VERR_WRONG_PARAMETER_COUNT); + + uintptr_t iParm = 0; + SHCLCLIENTCMDCTX cmdCtx; + RT_ZERO(cmdCtx); + if (cParms > VBOX_SHCL_CPARMS_DATA_WRITE_OLD) + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); + cmdCtx.uContextID = paParms[iParm].u.uint64; + iParm++; + } + else + { + /* Older Guest Additions (< 6.1) did not supply a context ID. + * We dig it out from our saved context ID list then a bit down below. */ + } + + if (cParms == VBOX_SHCL_CPARMS_DATA_WRITE_61B) + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + ASSERT_GUEST_RETURN(paParms[iParm].u.uint32 == 0, VERR_INVALID_FLAGS); + iParm++; + } + + SHCLFORMAT uFormat = VBOX_SHCL_FMT_NONE; + uint32_t cbData = 0; + void *pvData = NULL; + + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* Format bit. */ + uFormat = paParms[iParm].u.uint32; + iParm++; + if (cParms == VBOX_SHCL_CPARMS_DATA_WRITE_61B) + { + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* "cbData" - duplicates buffer size. */ + iParm++; + } + ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Data buffer */ + pvData = paParms[iParm].u.pointer.addr; + cbData = paParms[iParm].u.pointer.size; + iParm++; + Assert(iParm == cParms); + + /* + * Handle / check context ID. + */ + if (!fReportsContextID) /* Do we have to deal with old(er) GAs (< 6.1) which don't support context IDs? Dig out the context ID then. */ + { + PSHCLCLIENTLEGACYCID pCID = NULL; + PSHCLCLIENTLEGACYCID pCIDIter; + RTListForEach(&pClient->Legacy.lstCID, pCIDIter, SHCLCLIENTLEGACYCID, Node) /* Slow, but does the job for now. */ + { + if (pCIDIter->uFormat == uFormat) + { + pCID = pCIDIter; + break; + } + } + + ASSERT_GUEST_MSG_RETURN(pCID != NULL, ("Context ID for format %#x not found\n", uFormat), VERR_INVALID_CONTEXT); + cmdCtx.uContextID = pCID->uCID; + + /* Not needed anymore; clean up. */ + Assert(pClient->Legacy.cCID); + pClient->Legacy.cCID--; + RTListNodeRemove(&pCID->Node); + RTMemFree(pCID); + } + + uint64_t const idCtxExpected = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pClient->EventSrc.uID, + VBOX_SHCL_CONTEXTID_GET_EVENT(cmdCtx.uContextID)); + ASSERT_GUEST_MSG_RETURN(cmdCtx.uContextID == idCtxExpected, + ("Wrong context ID: %#RX64, expected %#RX64\n", cmdCtx.uContextID, idCtxExpected), + VERR_INVALID_CONTEXT); + + /* + * For some reason we need to do this (makes absolutely no sense to bird). + */ + /** @todo r=bird: I really don't get why you need the State.POD.uFormat + * member. I'm sure there is a reason. Incomplete code? */ + if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) + { + if (pClient->State.POD.uFormat == VBOX_SHCL_FMT_NONE) + pClient->State.POD.uFormat = uFormat; + } + +#ifdef LOG_ENABLED + char *pszFmt = ShClFormatsToStrA(uFormat); + if (pszFmt) + { + LogRel2(("Shared Clipboard: Guest writes %RU32 bytes clipboard data in format '%s' to host\n", cbData, pszFmt)); + RTStrFree(pszFmt); + } +#endif + + /* + * Write the data to the active host side clipboard. + */ + int rc = RTCritSectEnter(&g_CritSect); + AssertRCReturn(rc, rc); + + if (g_ExtState.pfnExtension) + { + SHCLEXTPARMS parms; + RT_ZERO(parms); + parms.uFormat = uFormat; + parms.u.pvData = pvData; + parms.cbData = cbData; + + g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_DATA_WRITE, &parms, sizeof(parms)); + rc = VINF_SUCCESS; + } + else + { + /* Let the backend implementation know. */ + rc = ShClBackendWriteData(&g_ShClBackend, pClient, &cmdCtx, uFormat, pvData, cbData); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Writing guest clipboard data to the host failed with %Rrc\n", rc)); + + int rc2; /* Don't return internals back to the guest. */ + rc2 = ShClSvcGuestDataSignal(pClient, &cmdCtx, uFormat, pvData, cbData); /* To complete pending events, if any. */ + if (RT_FAILURE(rc2)) + LogRel(("Shared Clipboard: Signalling host about guest clipboard data failed with %Rrc\n", rc2)); + AssertRC(rc2); + } + + RTCritSectLeave(&g_CritSect); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Gets an error from HGCM service parameters. + * + * @returns VBox status code. + * @param cParms Number of HGCM parameters supplied in \a paParms. + * @param paParms Array of HGCM parameters. + * @param pRc Where to store the received error code. + */ +static int shClSvcClientError(uint32_t cParms, VBOXHGCMSVCPARM paParms[], int *pRc) +{ + AssertPtrReturn(paParms, VERR_INVALID_PARAMETER); + AssertPtrReturn(pRc, VERR_INVALID_PARAMETER); + + int rc; + + if (cParms == VBOX_SHCL_CPARMS_ERROR) + { + rc = HGCMSvcGetU32(&paParms[1], (uint32_t *)pRc); /** @todo int vs. uint32_t !!! */ + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sets the transfer source type of a Shared Clipboard client. + * + * @returns VBox status code. + * @param pClient Client to set transfer source type for. + * @param enmSource Source type to set. + */ +int shClSvcSetSource(PSHCLCLIENT pClient, SHCLSOURCE enmSource) +{ + if (!pClient) /* If no client connected (anymore), bail out. */ + return VINF_SUCCESS; + + int rc = VINF_SUCCESS; + + if (ShClSvcLock()) + { + pClient->State.enmSource = enmSource; + + LogFlowFunc(("Source of client %RU32 is now %RU32\n", pClient->State.uClientID, pClient->State.enmSource)); + + ShClSvcUnlock(); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static int svcInit(VBOXHGCMSVCFNTABLE *pTable) +{ + int rc = RTCritSectInit(&g_CritSect); + + if (RT_SUCCESS(rc)) + { + shClSvcModeSet(VBOX_SHCL_MODE_OFF); + + rc = ShClBackendInit(ShClSvcGetBackend(), pTable); + + /* Clean up on failure, because 'svnUnload' will not be called + * if the 'svcInit' returns an error. + */ + if (RT_FAILURE(rc)) + { + RTCritSectDelete(&g_CritSect); + } + } + + return rc; +} + +static DECLCALLBACK(int) svcUnload(void *) +{ + LogFlowFuncEnter(); + + ShClBackendDestroy(ShClSvcGetBackend()); + + RTCritSectDelete(&g_CritSect); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) svcDisconnect(void *, uint32_t u32ClientID, void *pvClient) +{ + LogFunc(("u32ClientID=%RU32\n", u32ClientID)); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; + AssertPtr(pClient); + + /* In order to communicate with guest service, HGCM VRDP clipboard extension + * needs to know its connection client ID. Currently, in svcConnect() we always + * cache ID of the first ever connected client. When client disconnects, + * we need to forget its ID and let svcConnect() to pick up the next ID when a new + * connection will be requested by guest service (see #10115). */ + if (g_ExtState.uClientID == u32ClientID) + { + g_ExtState.uClientID = 0; + } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClSvcClientTransfersReset(pClient); +#endif + + ShClBackendDisconnect(&g_ShClBackend, pClient); + + shClSvcClientDestroy(pClient); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) svcConnect(void *, uint32_t u32ClientID, void *pvClient, uint32_t fRequestor, bool fRestoring) +{ + RT_NOREF(fRequestor, fRestoring); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; + AssertPtr(pvClient); + + int rc = shClSvcClientInit(pClient, u32ClientID); + if (RT_SUCCESS(rc)) + { + /* Assign weak pointer to client map. */ + /** @todo r=bird: The g_mapClients is only there for looking up + * g_ExtState.uClientID (unserialized btw), so why not use store the + * pClient value directly in g_ExtState instead of the ID? It cannot + * crash any worse that racing map insertion/removal. */ + g_mapClients[u32ClientID] = pClient; /** @todo Handle OOM / collisions? */ + rc = ShClBackendConnect(&g_ShClBackend, pClient, ShClSvcGetHeadless()); + if (RT_SUCCESS(rc)) + { + /* Sync the host clipboard content with the client. */ + rc = ShClBackendSync(&g_ShClBackend, pClient); + if (RT_SUCCESS(rc)) + { + /* For now we ASSUME that the first client that connects is in charge for + communicating with the service extension. */ + /** @todo This isn't optimal, but only the guest really knows which client is in + * focus on the console. See @bugref{10115} for details. */ + if (g_ExtState.uClientID == 0) + g_ExtState.uClientID = u32ClientID; + + /* The sync could return VINF_NO_CHANGE if nothing has changed on the host, but + older Guest Additions didn't use RT_SUCCESS to but == VINF_SUCCESS to check for + success. So just return VINF_SUCCESS here to not break older Guest Additions. */ + LogFunc(("Successfully connected client %#x%s\n", + u32ClientID, g_ExtState.uClientID == u32ClientID ? " - Use by ExtState too" : "")); + return VINF_SUCCESS; + } + + LogFunc(("ShClBackendSync failed: %Rrc\n", rc)); + ShClBackendDisconnect(&g_ShClBackend, pClient); + } + else + LogFunc(("ShClBackendConnect failed: %Rrc\n", rc)); + shClSvcClientDestroy(pClient); + } + else + LogFunc(("shClSvcClientInit failed: %Rrc\n", rc)); + LogFlowFuncLeaveRC(rc); + return rc; +} + +static DECLCALLBACK(void) svcCall(void *, + VBOXHGCMCALLHANDLE callHandle, + uint32_t u32ClientID, + void *pvClient, + uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM paParms[], + uint64_t tsArrival) +{ + RT_NOREF(u32ClientID, pvClient, tsArrival); + PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; + AssertPtr(pClient); + +#ifdef LOG_ENABLED + Log2Func(("u32ClientID=%RU32, fn=%RU32 (%s), cParms=%RU32, paParms=%p\n", + u32ClientID, u32Function, ShClGuestMsgToStr(u32Function), cParms, paParms)); + for (uint32_t i = 0; i < cParms; i++) + { + switch (paParms[i].type) + { + case VBOX_HGCM_SVC_PARM_32BIT: + Log3Func((" paParms[%RU32]: type uint32_t - value %RU32\n", i, paParms[i].u.uint32)); + break; + case VBOX_HGCM_SVC_PARM_64BIT: + Log3Func((" paParms[%RU32]: type uint64_t - value %RU64\n", i, paParms[i].u.uint64)); + break; + case VBOX_HGCM_SVC_PARM_PTR: + Log3Func((" paParms[%RU32]: type ptr - value 0x%p (%RU32 bytes)\n", + i, paParms[i].u.pointer.addr, paParms[i].u.pointer.size)); + break; + case VBOX_HGCM_SVC_PARM_PAGES: + Log3Func((" paParms[%RU32]: type pages - cb=%RU32, cPages=%RU16\n", + i, paParms[i].u.Pages.cb, paParms[i].u.Pages.cPages)); + break; + default: + AssertFailed(); + } + } + Log2Func(("Client state: fFlags=0x%x, fGuestFeatures0=0x%x, fGuestFeatures1=0x%x\n", + pClient->State.fFlags, pClient->State.fGuestFeatures0, pClient->State.fGuestFeatures1)); +#endif + + int rc; + switch (u32Function) + { + case VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT: + RTCritSectEnter(&pClient->CritSect); + rc = shClSvcClientMsgOldGet(pClient, callHandle, cParms, paParms); + RTCritSectLeave(&pClient->CritSect); + break; + + case VBOX_SHCL_GUEST_FN_CONNECT: + LogRel(("Shared Clipboard: 6.1.0 beta or rc Guest Additions detected. Please upgrade!\n")); + rc = VERR_NOT_IMPLEMENTED; + break; + + case VBOX_SHCL_GUEST_FN_NEGOTIATE_CHUNK_SIZE: + rc = shClSvcClientNegogiateChunkSize(pClient, callHandle, cParms, paParms); + break; + + case VBOX_SHCL_GUEST_FN_REPORT_FEATURES: + rc = shClSvcClientReportFeatures(pClient, callHandle, cParms, paParms); + break; + + case VBOX_SHCL_GUEST_FN_QUERY_FEATURES: + rc = shClSvcClientQueryFeatures(callHandle, cParms, paParms); + break; + + case VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT: + RTCritSectEnter(&pClient->CritSect); + rc = shClSvcClientMsgPeek(pClient, callHandle, cParms, paParms, false /*fWait*/); + RTCritSectLeave(&pClient->CritSect); + break; + + case VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT: + RTCritSectEnter(&pClient->CritSect); + rc = shClSvcClientMsgPeek(pClient, callHandle, cParms, paParms, true /*fWait*/); + RTCritSectLeave(&pClient->CritSect); + break; + + case VBOX_SHCL_GUEST_FN_MSG_GET: + RTCritSectEnter(&pClient->CritSect); + rc = shClSvcClientMsgGet(pClient, callHandle, cParms, paParms); + RTCritSectLeave(&pClient->CritSect); + break; + + case VBOX_SHCL_GUEST_FN_MSG_CANCEL: + RTCritSectEnter(&pClient->CritSect); + rc = shClSvcClientMsgCancel(pClient, cParms); + RTCritSectLeave(&pClient->CritSect); + break; + + case VBOX_SHCL_GUEST_FN_REPORT_FORMATS: + rc = shClSvcClientReportFormats(pClient, cParms, paParms); + break; + + case VBOX_SHCL_GUEST_FN_DATA_READ: + rc = shClSvcClientReadData(pClient, cParms, paParms); + break; + + case VBOX_SHCL_GUEST_FN_DATA_WRITE: + rc = shClSvcClientWriteData(pClient, cParms, paParms); + break; + + case VBOX_SHCL_GUEST_FN_ERROR: + { + int rcGuest; + rc = shClSvcClientError(cParms,paParms, &rcGuest); + if (RT_SUCCESS(rc)) + { + LogRel(("Shared Clipboard: Error reported from guest side: %Rrc\n", rcGuest)); + + shClSvcClientLock(pClient); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClSvcClientTransfersReset(pClient); +#endif + shClSvcClientUnlock(pClient); + } + break; + } + + default: + { +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if ( u32Function <= VBOX_SHCL_GUEST_FN_LAST + && (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID) ) + { + if (g_fTransferMode & VBOX_SHCL_TRANSFER_MODE_ENABLED) + rc = shClSvcTransferHandler(pClient, callHandle, u32Function, cParms, paParms, tsArrival); + else + { + LogRel2(("Shared Clipboard: File transfers are disabled for this VM\n")); + rc = VERR_ACCESS_DENIED; + } + } + else +#endif + { + LogRel2(("Shared Clipboard: Unknown guest function: %u (%#x)\n", u32Function, u32Function)); + rc = VERR_NOT_IMPLEMENTED; + } + break; + } + } + + LogFlowFunc(("[Client %RU32] rc=%Rrc\n", pClient->State.uClientID, rc)); + + if (rc != VINF_HGCM_ASYNC_EXECUTE) + g_pHelpers->pfnCallComplete(callHandle, rc); +} + +/** + * Initializes a Shared Clipboard service's client state. + * + * @returns VBox status code. + * @param pClientState Client state to initialize. + * @param uClientID Client ID (HGCM) to use for this client state. + */ +int shClSvcClientStateInit(PSHCLCLIENTSTATE pClientState, uint32_t uClientID) +{ + LogFlowFuncEnter(); + + shclSvcClientStateReset(pClientState); + + /* Register the client. */ + pClientState->uClientID = uClientID; + + return VINF_SUCCESS; +} + +/** + * Destroys a Shared Clipboard service's client state. + * + * @returns VBox status code. + * @param pClientState Client state to destroy. + */ +int shClSvcClientStateDestroy(PSHCLCLIENTSTATE pClientState) +{ + RT_NOREF(pClientState); + + LogFlowFuncEnter(); + + return VINF_SUCCESS; +} + +/** + * Resets a Shared Clipboard service's client state. + * + * @param pClientState Client state to reset. + */ +void shclSvcClientStateReset(PSHCLCLIENTSTATE pClientState) +{ + LogFlowFuncEnter(); + + pClientState->fGuestFeatures0 = VBOX_SHCL_GF_NONE; + pClientState->fGuestFeatures1 = VBOX_SHCL_GF_NONE; + + pClientState->cbChunkSize = VBOX_SHCL_DEFAULT_CHUNK_SIZE; /** @todo Make this configurable. */ + pClientState->enmSource = SHCLSOURCE_INVALID; + pClientState->fFlags = SHCLCLIENTSTATE_FLAGS_NONE; + + pClientState->POD.enmDir = SHCLTRANSFERDIR_UNKNOWN; + pClientState->POD.uFormat = VBOX_SHCL_FMT_NONE; + pClientState->POD.cbToReadWriteTotal = 0; + pClientState->POD.cbReadWritten = 0; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + pClientState->Transfers.enmTransferDir = SHCLTRANSFERDIR_UNKNOWN; +#endif +} + +/* + * We differentiate between a function handler for the guest and one for the host. + */ +static DECLCALLBACK(int) svcHostCall(void *, + uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM paParms[]) +{ + int rc = VINF_SUCCESS; + + LogFlowFunc(("u32Function=%RU32 (%s), cParms=%RU32, paParms=%p\n", + u32Function, ShClHostFunctionToStr(u32Function), cParms, paParms)); + + switch (u32Function) + { + case VBOX_SHCL_HOST_FN_SET_MODE: + { + if (cParms != 1) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + uint32_t u32Mode = VBOX_SHCL_MODE_OFF; + + rc = HGCMSvcGetU32(&paParms[0], &u32Mode); + if (RT_SUCCESS(rc)) + rc = shClSvcModeSet(u32Mode); + } + + break; + } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + case VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE: + { + if (cParms != 1) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + uint32_t fTransferMode; + rc = HGCMSvcGetU32(&paParms[0], &fTransferMode); + if (RT_SUCCESS(rc)) + rc = shClSvcTransferModeSet(fTransferMode); + } + break; + } +#endif + case VBOX_SHCL_HOST_FN_SET_HEADLESS: + { + if (cParms != 1) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + uint32_t uHeadless; + rc = HGCMSvcGetU32(&paParms[0], &uHeadless); + if (RT_SUCCESS(rc)) + { + g_fHeadless = RT_BOOL(uHeadless); + LogRel(("Shared Clipboard: Service running in %s mode\n", g_fHeadless ? "headless" : "normal")); + } + } + break; + } + + default: + { +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + rc = shClSvcTransferHostHandler(u32Function, cParms, paParms); +#else + rc = VERR_NOT_IMPLEMENTED; +#endif + break; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +#ifndef UNIT_TEST + +/** + * SSM descriptor table for the SHCLCLIENTLEGACYCID structure. + * + * @note Saving the ListEntry attribute is not necessary, as this gets used on runtime only. + */ +static SSMFIELD const s_aShClSSMClientLegacyCID[] = +{ + SSMFIELD_ENTRY(SHCLCLIENTLEGACYCID, uCID), + SSMFIELD_ENTRY(SHCLCLIENTLEGACYCID, enmType), + SSMFIELD_ENTRY(SHCLCLIENTLEGACYCID, uFormat), + SSMFIELD_ENTRY_TERM() +}; + +/** + * SSM descriptor table for the SHCLCLIENTSTATE structure. + * + * @note Saving the session ID not necessary, as they're not persistent across + * state save/restore. + */ +static SSMFIELD const s_aShClSSMClientState[] = +{ + SSMFIELD_ENTRY(SHCLCLIENTSTATE, fGuestFeatures0), + SSMFIELD_ENTRY(SHCLCLIENTSTATE, fGuestFeatures1), + SSMFIELD_ENTRY(SHCLCLIENTSTATE, cbChunkSize), + SSMFIELD_ENTRY(SHCLCLIENTSTATE, enmSource), + SSMFIELD_ENTRY(SHCLCLIENTSTATE, fFlags), + SSMFIELD_ENTRY_TERM() +}; + +/** + * VBox 6.1 Beta 1 version of s_aShClSSMClientState (no flags). + */ +static SSMFIELD const s_aShClSSMClientState61B1[] = +{ + SSMFIELD_ENTRY(SHCLCLIENTSTATE, fGuestFeatures0), + SSMFIELD_ENTRY(SHCLCLIENTSTATE, fGuestFeatures1), + SSMFIELD_ENTRY(SHCLCLIENTSTATE, cbChunkSize), + SSMFIELD_ENTRY(SHCLCLIENTSTATE, enmSource), + SSMFIELD_ENTRY_TERM() +}; + +/** + * SSM descriptor table for the SHCLCLIENTPODSTATE structure. + */ +static SSMFIELD const s_aShClSSMClientPODState[] = +{ + SSMFIELD_ENTRY(SHCLCLIENTPODSTATE, enmDir), + SSMFIELD_ENTRY(SHCLCLIENTPODSTATE, uFormat), + SSMFIELD_ENTRY(SHCLCLIENTPODSTATE, cbToReadWriteTotal), + SSMFIELD_ENTRY(SHCLCLIENTPODSTATE, cbReadWritten), + SSMFIELD_ENTRY(SHCLCLIENTPODSTATE, tsLastReadWrittenMs), + SSMFIELD_ENTRY_TERM() +}; + +/** + * SSM descriptor table for the SHCLCLIENTURISTATE structure. + */ +static SSMFIELD const s_aShClSSMClientTransferState[] = +{ + SSMFIELD_ENTRY(SHCLCLIENTTRANSFERSTATE, enmTransferDir), + SSMFIELD_ENTRY_TERM() +}; + +/** + * SSM descriptor table for the header of the SHCLCLIENTMSG structure. + * The actual message parameters will be serialized separately. + */ +static SSMFIELD const s_aShClSSMClientMsgHdr[] = +{ + SSMFIELD_ENTRY(SHCLCLIENTMSG, idMsg), + SSMFIELD_ENTRY(SHCLCLIENTMSG, cParms), + SSMFIELD_ENTRY_TERM() +}; + +/** + * SSM descriptor table for what used to be the VBOXSHCLMSGCTX structure but is + * now part of SHCLCLIENTMSG. + */ +static SSMFIELD const s_aShClSSMClientMsgCtx[] = +{ + SSMFIELD_ENTRY(SHCLCLIENTMSG, idCtx), + SSMFIELD_ENTRY_TERM() +}; +#endif /* !UNIT_TEST */ + +static DECLCALLBACK(int) svcSaveState(void *, uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM) +{ + LogFlowFuncEnter(); + +#ifndef UNIT_TEST + /* + * When the state will be restored, pending requests will be reissued + * by VMMDev. The service therefore must save state as if there were no + * pending request. + * Pending requests, if any, will be completed in svcDisconnect. + */ + RT_NOREF(u32ClientID); + LogFunc(("u32ClientID=%RU32\n", u32ClientID)); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; + AssertPtr(pClient); + + /* Write Shared Clipboard saved state version. */ + pVMM->pfnSSMR3PutU32(pSSM, VBOX_SHCL_SAVED_STATE_VER_CURRENT); + + int rc = pVMM->pfnSSMR3PutStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /*fFlags*/, &s_aShClSSMClientState[0], NULL); + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3PutStructEx(pSSM, &pClient->State.POD, sizeof(pClient->State.POD), 0 /*fFlags*/, &s_aShClSSMClientPODState[0], NULL); + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3PutStructEx(pSSM, &pClient->State.Transfers, sizeof(pClient->State.Transfers), 0 /*fFlags*/, &s_aShClSSMClientTransferState[0], NULL); + AssertRCReturn(rc, rc); + + /* Serialize the client's internal message queue. */ + rc = pVMM->pfnSSMR3PutU64(pSSM, pClient->cMsgAllocated); + AssertRCReturn(rc, rc); + + PSHCLCLIENTMSG pMsg; + RTListForEach(&pClient->MsgQueue, pMsg, SHCLCLIENTMSG, ListEntry) + { + pVMM->pfnSSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, &s_aShClSSMClientMsgHdr[0], NULL); + pVMM->pfnSSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, &s_aShClSSMClientMsgCtx[0], NULL); + + for (uint32_t iParm = 0; iParm < pMsg->cParms; iParm++) + HGCMSvcSSMR3Put(&pMsg->aParms[iParm], pSSM, pVMM); + } + + rc = pVMM->pfnSSMR3PutU64(pSSM, pClient->Legacy.cCID); + AssertRCReturn(rc, rc); + + PSHCLCLIENTLEGACYCID pCID; + RTListForEach(&pClient->Legacy.lstCID, pCID, SHCLCLIENTLEGACYCID, Node) + { + rc = pVMM->pfnSSMR3PutStructEx(pSSM, pCID, sizeof(SHCLCLIENTLEGACYCID), 0 /*fFlags*/, &s_aShClSSMClientLegacyCID[0], NULL); + AssertRCReturn(rc, rc); + } +#else /* UNIT_TEST */ + RT_NOREF(u32ClientID, pvClient, pSSM, pVMM); +#endif /* UNIT_TEST */ + return VINF_SUCCESS; +} + +#ifndef UNIT_TEST +static int svcLoadStateV0(uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion) +{ + RT_NOREF(u32ClientID, pvClient, pSSM, uVersion); + + uint32_t uMarker; + int rc = pVMM->pfnSSMR3GetU32(pSSM, &uMarker); /* Begin marker. */ + AssertRC(rc); + Assert(uMarker == UINT32_C(0x19200102) /* SSMR3STRUCT_BEGIN */); + + rc = pVMM->pfnSSMR3Skip(pSSM, sizeof(uint32_t)); /* Client ID */ + AssertRCReturn(rc, rc); + + bool fValue; + rc = pVMM->pfnSSMR3GetBool(pSSM, &fValue); /* fHostMsgQuit */ + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3GetBool(pSSM, &fValue); /* fHostMsgReadData */ + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3GetBool(pSSM, &fValue); /* fHostMsgFormats */ + AssertRCReturn(rc, rc); + + uint32_t fFormats; + rc = pVMM->pfnSSMR3GetU32(pSSM, &fFormats); /* u32RequestedFormat */ + AssertRCReturn(rc, rc); + + rc = pVMM->pfnSSMR3GetU32(pSSM, &uMarker); /* End marker. */ + AssertRCReturn(rc, rc); + Assert(uMarker == UINT32_C(0x19920406) /* SSMR3STRUCT_END */); + + return VINF_SUCCESS; +} +#endif /* UNIT_TEST */ + +static DECLCALLBACK(int) svcLoadState(void *, uint32_t u32ClientID, void *pvClient, + PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion) +{ + LogFlowFuncEnter(); + +#ifndef UNIT_TEST + + RT_NOREF(u32ClientID, uVersion); + + PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; + AssertPtr(pClient); + + /* Restore the client data. */ + uint32_t lenOrVer; + int rc = pVMM->pfnSSMR3GetU32(pSSM, &lenOrVer); + AssertRCReturn(rc, rc); + + LogFunc(("u32ClientID=%RU32, lenOrVer=%#RX64\n", u32ClientID, lenOrVer)); + + if (lenOrVer == VBOX_SHCL_SAVED_STATE_VER_3_1) + return svcLoadStateV0(u32ClientID, pvClient, pSSM, pVMM, uVersion); + + if ( lenOrVer >= VBOX_SHCL_SAVED_STATE_VER_6_1B2 + && lenOrVer <= VBOX_SHCL_SAVED_STATE_VER_CURRENT) + { + if (lenOrVer >= VBOX_SHCL_SAVED_STATE_VER_6_1RC1) + { + pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, + &s_aShClSSMClientState[0], NULL); + pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State.POD, sizeof(pClient->State.POD), 0 /* fFlags */, + &s_aShClSSMClientPODState[0], NULL); + } + else + pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, + &s_aShClSSMClientState61B1[0], NULL); + rc = pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State.Transfers, sizeof(pClient->State.Transfers), 0 /* fFlags */, + &s_aShClSSMClientTransferState[0], NULL); + AssertRCReturn(rc, rc); + + /* Load the client's internal message queue. */ + uint64_t cMsgs; + rc = pVMM->pfnSSMR3GetU64(pSSM, &cMsgs); + AssertRCReturn(rc, rc); + AssertLogRelMsgReturn(cMsgs < _16K, ("Too many messages: %u (%x)\n", cMsgs, cMsgs), VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + + for (uint64_t i = 0; i < cMsgs; i++) + { + union + { + SHCLCLIENTMSG Msg; + uint8_t abPadding[RT_UOFFSETOF(SHCLCLIENTMSG, aParms) + sizeof(VBOXHGCMSVCPARM) * 2]; + } u; + + pVMM->pfnSSMR3GetStructEx(pSSM, &u.Msg, RT_UOFFSETOF(SHCLCLIENTMSG, aParms), 0 /*fFlags*/, + &s_aShClSSMClientMsgHdr[0], NULL); + rc = pVMM->pfnSSMR3GetStructEx(pSSM, &u.Msg, RT_UOFFSETOF(SHCLCLIENTMSG, aParms), 0 /*fFlags*/, + &s_aShClSSMClientMsgCtx[0], NULL); + AssertRCReturn(rc, rc); + + AssertLogRelMsgReturn(u.Msg.cParms <= VMMDEV_MAX_HGCM_PARMS, + ("Too many HGCM message parameters: %u (%#x)\n", u.Msg.cParms, u.Msg.cParms), + VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + + PSHCLCLIENTMSG pMsg = shClSvcMsgAlloc(pClient, u.Msg.idMsg, u.Msg.cParms); + AssertReturn(pMsg, VERR_NO_MEMORY); + pMsg->idCtx = u.Msg.idCtx; + + for (uint32_t p = 0; p < pMsg->cParms; p++) + { + rc = HGCMSvcSSMR3Get(&pMsg->aParms[p], pSSM, pVMM); + AssertRCReturnStmt(rc, shClSvcMsgFree(pClient, pMsg), rc); + } + + RTCritSectEnter(&pClient->CritSect); + shClSvcMsgAdd(pClient, pMsg, true /* fAppend */); + RTCritSectLeave(&pClient->CritSect); + } + + if (lenOrVer >= VBOX_SHCL_SAVED_STATE_LEGACY_CID) + { + uint64_t cCID; + rc = pVMM->pfnSSMR3GetU64(pSSM, &cCID); + AssertRCReturn(rc, rc); + AssertLogRelMsgReturn(cCID < _16K, ("Too many context IDs: %u (%x)\n", cCID, cCID), VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + + for (uint64_t i = 0; i < cCID; i++) + { + PSHCLCLIENTLEGACYCID pCID = (PSHCLCLIENTLEGACYCID)RTMemAlloc(sizeof(SHCLCLIENTLEGACYCID)); + AssertPtrReturn(pCID, VERR_NO_MEMORY); + + pVMM->pfnSSMR3GetStructEx(pSSM, pCID, sizeof(SHCLCLIENTLEGACYCID), 0 /* fFlags */, + &s_aShClSSMClientLegacyCID[0], NULL); + RTListAppend(&pClient->Legacy.lstCID, &pCID->Node); + } + } + } + else + { + LogRel(("Shared Clipboard: Unsupported saved state version (%#x)\n", lenOrVer)); + return VERR_SSM_DATA_UNIT_FORMAT_CHANGED; + } + + /* Actual host data are to be reported to guest (SYNC). */ + ShClBackendSync(&g_ShClBackend, pClient); + +#else /* UNIT_TEST */ + RT_NOREF(u32ClientID, pvClient, pSSM, pVMM, uVersion); +#endif /* UNIT_TEST */ + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) extCallback(uint32_t u32Function, uint32_t u32Format, void *pvData, uint32_t cbData) +{ + RT_NOREF(pvData, cbData); + + LogFlowFunc(("u32Function=%RU32\n", u32Function)); + + int rc = VINF_SUCCESS; + + /* Figure out if the client in charge for the service extension still is connected. */ + ClipboardClientMap::const_iterator itClient = g_mapClients.find(g_ExtState.uClientID); + if (itClient != g_mapClients.end()) + { + PSHCLCLIENT pClient = itClient->second; + AssertPtr(pClient); + + switch (u32Function) + { + /* The service extension announces formats to the guest. */ + case VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE: + { + LogFlowFunc(("VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE: g_ExtState.fReadingData=%RTbool\n", g_ExtState.fReadingData)); + if (!g_ExtState.fReadingData) + rc = ShClSvcHostReportFormats(pClient, u32Format); + else + { + g_ExtState.fDelayedAnnouncement = true; + g_ExtState.fDelayedFormats = u32Format; + rc = VINF_SUCCESS; + } + break; + } + + /* The service extension wants read data from the guest. */ + case VBOX_CLIPBOARD_EXT_FN_DATA_READ: + rc = ShClSvcGuestDataRequest(pClient, u32Format, NULL /* pidEvent */); + break; + + default: + /* Just skip other messages. */ + break; + } + } + else + rc = VERR_NOT_FOUND; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static DECLCALLBACK(int) svcRegisterExtension(void *, PFNHGCMSVCEXT pfnExtension, void *pvExtension) +{ + LogFlowFunc(("pfnExtension=%p\n", pfnExtension)); + + SHCLEXTPARMS parms; + RT_ZERO(parms); + + /* + * Reference counting for service extension registration is done a few + * layers up (in ConsoleVRDPServer::ClipboardCreate()). + */ + + int rc = RTCritSectEnter(&g_CritSect); + AssertLogRelRCReturn(rc, rc); + + if (pfnExtension) + { + /* Install extension. */ + g_ExtState.pfnExtension = pfnExtension; + g_ExtState.pvExtension = pvExtension; + + parms.u.pfnCallback = extCallback; + g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); + + LogRel2(("Shared Clipboard: registered service extension\n")); + } + else + { + if (g_ExtState.pfnExtension) + g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); + + /* Uninstall extension. */ + g_ExtState.pvExtension = NULL; + g_ExtState.pfnExtension = NULL; + + LogRel2(("Shared Clipboard: de-registered service extension\n")); + } + + RTCritSectLeave(&g_CritSect); + + return VINF_SUCCESS; +} + +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTable) +{ + int rc = VINF_SUCCESS; + + LogFlowFunc(("pTable=%p\n", pTable)); + + if (!RT_VALID_PTR(pTable)) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + LogFunc(("pTable->cbSize = %d, ptable->u32Version = 0x%08X\n", pTable->cbSize, pTable->u32Version)); + + if ( pTable->cbSize != sizeof (VBOXHGCMSVCFNTABLE) + || pTable->u32Version != VBOX_HGCM_SVC_VERSION) + { + rc = VERR_VERSION_MISMATCH; + } + else + { + g_pHelpers = pTable->pHelpers; + + pTable->cbClient = sizeof(SHCLCLIENT); + + /* Map legacy clients to root. */ + pTable->idxLegacyClientCategory = HGCM_CLIENT_CATEGORY_ROOT; + + /* Limit the number of clients to 128 in each category (should be enough), + but set kernel clients to 1. */ + for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) + pTable->acMaxClients[i] = 128; + pTable->acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL] = 1; + + /* Only 16 pending calls per client (1 should be enough). */ + for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) + pTable->acMaxCallsPerClient[i] = 16; + + pTable->pfnUnload = svcUnload; + pTable->pfnConnect = svcConnect; + pTable->pfnDisconnect = svcDisconnect; + pTable->pfnCall = svcCall; + pTable->pfnHostCall = svcHostCall; + pTable->pfnSaveState = svcSaveState; + pTable->pfnLoadState = svcLoadState; + pTable->pfnRegisterExtension = svcRegisterExtension; + pTable->pfnNotify = NULL; + pTable->pvService = NULL; + + /* Service specific initialization. */ + rc = svcInit(pTable); + } + } + + LogFlowFunc(("Returning %Rrc\n", rc)); + return rc; +} diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.rc b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.rc new file mode 100644 index 00000000..7e17a0b5 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.rc @@ -0,0 +1,61 @@ +/* $Id: VBoxSharedClipboardSvc.rc $ */ +/** @file + * Shared Clipboard Service - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Shared Clipboard Host Service\0" + VALUE "InternalName", "VBoxSharedClipboard\0" + VALUE "OriginalFilename", "VBoxSharedClipboard.dll\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.cpp b/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.cpp new file mode 100644 index 00000000..290763be --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.cpp @@ -0,0 +1,718 @@ +/* $Id: darwin-pasteboard.cpp $ */ +/** @file + * Shared Clipboard Service - Mac OS X host implementation. + */ + +/* + * Includes contributions from François Revol + * + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include <Carbon/Carbon.h> + +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/errcore.h> +#include <iprt/utf16.h> + +#include <VBox/log.h> +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <VBox/GuestHost/SharedClipboard.h> +#include <VBox/GuestHost/clipboard-helper.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define WITH_HTML_H2G 1 +#define WITH_HTML_G2H 1 + +/* For debugging */ +//#define SHOW_CLIPBOARD_CONTENT + + +/** + * Initialize the global pasteboard and return a reference to it. + * + * @param pPasteboardRef Reference to the global pasteboard. + * + * @returns IPRT status code. + */ +int initPasteboard(PasteboardRef *pPasteboardRef) +{ + int rc = VINF_SUCCESS; + + if (PasteboardCreate(kPasteboardClipboard, pPasteboardRef)) + rc = VERR_NOT_SUPPORTED; + + return rc; +} + +/** + * Release the reference to the global pasteboard. + * + * @param pPasteboardRef Reference to the global pasteboard. + */ +void destroyPasteboard(PasteboardRef *pPasteboardRef) +{ + CFRelease(*pPasteboardRef); + *pPasteboardRef = NULL; +} + +/** + * Inspect the global pasteboard for new content. Check if there is some type + * that is supported by vbox and return it. + * + * @param hPasteboard Reference to the global pasteboard. + * @param idOwnership Our ownership ID. + * @param hStrOwnershipFlavor The ownership flavor string reference returned + * by takePasteboardOwnership(). + * @param pfFormats Pointer for the bit combination of the + * supported types. + * @param pfChanged True if something has changed after the + * last call. + * + * @returns VINF_SUCCESS. + */ +int queryNewPasteboardFormats(PasteboardRef hPasteboard, uint64_t idOwnership, void *hStrOwnershipFlavor, + uint32_t *pfFormats, bool *pfChanged) +{ + OSStatus orc; + + *pfFormats = 0; + *pfChanged = true; + + PasteboardSyncFlags syncFlags; + /* Make sure all is in sync */ + syncFlags = PasteboardSynchronize(hPasteboard); + /* If nothing changed return */ + if (!(syncFlags & kPasteboardModified)) + { + *pfChanged = false; + Log2(("queryNewPasteboardFormats: no change\n")); + return VINF_SUCCESS; + } + + /* Are some items in the pasteboard? */ + ItemCount cItems = 0; + orc = PasteboardGetItemCount(hPasteboard, &cItems); + if (orc == 0) + { + if (cItems < 1) + Log(("queryNewPasteboardFormats: changed: No items on the pasteboard\n")); + else + { + /* The id of the first element in the pasteboard */ + PasteboardItemID idItem = 0; + orc = PasteboardGetItemIdentifier(hPasteboard, 1, &idItem); + if (orc == 0) + { + /* + * Retrieve all flavors on the pasteboard, maybe there + * is something we can use. Or maybe we're the owner. + */ + CFArrayRef hFlavors = 0; + orc = PasteboardCopyItemFlavors(hPasteboard, idItem, &hFlavors); + if (orc == 0) + { + CFIndex cFlavors = CFArrayGetCount(hFlavors); + for (CFIndex idxFlavor = 0; idxFlavor < cFlavors; idxFlavor++) + { + CFStringRef hStrFlavor = (CFStringRef)CFArrayGetValueAtIndex(hFlavors, idxFlavor); + if ( idItem == (PasteboardItemID)idOwnership + && hStrOwnershipFlavor + && CFStringCompare(hStrFlavor, (CFStringRef)hStrOwnershipFlavor, 0) == kCFCompareEqualTo) + { + /* We made the changes ourselves. */ + Log2(("queryNewPasteboardFormats: no-changed: our clipboard!\n")); + *pfChanged = false; + *pfFormats = 0; + break; + } + + if (UTTypeConformsTo(hStrFlavor, kUTTypeBMP)) + { + Log(("queryNewPasteboardFormats: BMP flavor detected.\n")); + *pfFormats |= VBOX_SHCL_FMT_BITMAP; + } + else if ( UTTypeConformsTo(hStrFlavor, kUTTypeUTF8PlainText) + || UTTypeConformsTo(hStrFlavor, kUTTypeUTF16PlainText)) + { + Log(("queryNewPasteboardFormats: Unicode flavor detected.\n")); + *pfFormats |= VBOX_SHCL_FMT_UNICODETEXT; + } +#ifdef WITH_HTML_H2G + else if (UTTypeConformsTo(hStrFlavor, kUTTypeHTML)) + { + Log(("queryNewPasteboardFormats: HTML flavor detected.\n")); + *pfFormats |= VBOX_SHCL_FMT_HTML; + } +#endif +#ifdef LOG_ENABLED + else if (LogIs2Enabled()) + { + if (CFStringGetCharactersPtr(hStrFlavor)) + Log2(("queryNewPasteboardFormats: Unknown flavor: %ls.\n", CFStringGetCharactersPtr(hStrFlavor))); + else if (CFStringGetCStringPtr(hStrFlavor, kCFStringEncodingUTF8)) + Log2(("queryNewPasteboardFormats: Unknown flavor: %s.\n", + CFStringGetCStringPtr(hStrFlavor, kCFStringEncodingUTF8))); + else + Log2(("queryNewPasteboardFormats: Unknown flavor: ???\n")); + } +#endif + } + + CFRelease(hFlavors); + } + else + Log(("queryNewPasteboardFormats: PasteboardCopyItemFlavors failed - %d (%#x)\n", orc, orc)); + } + else + Log(("queryNewPasteboardFormats: PasteboardGetItemIdentifier failed - %d (%#x)\n", orc, orc)); + + if (*pfChanged) + Log(("queryNewPasteboardFormats: changed: *pfFormats=%#x\n", *pfFormats)); + } + } + else + Log(("queryNewPasteboardFormats: PasteboardGetItemCount failed - %d (%#x)\n", orc, orc)); + return VINF_SUCCESS; +} + +/** + * Read content from the host clipboard and write it to the internal clipboard + * structure for further processing. + * + * @param pPasteboard Reference to the global pasteboard. + * @param fFormat The format type which should be read. + * @param pv The destination buffer. + * @param cb The size of the destination buffer. + * @param pcbActual The size which is needed to transfer the content. + * + * @returns IPRT status code. + */ +int readFromPasteboard(PasteboardRef pPasteboard, uint32_t fFormat, void *pv, uint32_t cb, uint32_t *pcbActual) +{ + Log(("readFromPasteboard: fFormat = %02X\n", fFormat)); + + /* Make sure all is in sync */ + PasteboardSynchronize(pPasteboard); + + /* Are some items in the pasteboard? */ + ItemCount cItems; + OSStatus orc = PasteboardGetItemCount(pPasteboard, &cItems); + if (cItems < 1) + return VINF_SUCCESS; + + /* + * Our default response... + */ + int rc = VERR_NOT_SUPPORTED; + + /* + * The id of the first element in the pasteboard + */ + PasteboardItemID idItem; + orc = PasteboardGetItemIdentifier(pPasteboard, 1, &idItem); + if (orc == 0) + { + CFDataRef hDataCopy = 0; + size_t cbDataCopy = 0; + + /* + * The guest request unicode + */ + if (fFormat & VBOX_SHCL_FMT_UNICODETEXT) + { + PRTUTF16 pwszSrcFree = NULL; + PCRTUTF16 pwszSrc = NULL; + size_t cwcSrc = 0; + + /* First preference is plain UTF-16 text: */ + orc = PasteboardCopyItemFlavorData(pPasteboard, idItem, kUTTypeUTF16PlainText, &hDataCopy); + if (orc == 0) + { + cbDataCopy = CFDataGetLength(hDataCopy); + Log(("Clipboard content is utf-16 (%zu bytes)\n", cbDataCopy)); + pwszSrc = (PCRTUTF16)CFDataGetBytePtr(hDataCopy); + if (pwszSrc) + { + cwcSrc = RTUtf16NLen(pwszSrc, cbDataCopy / sizeof(RTUTF16)); + if (cwcSrc >= cbDataCopy / sizeof(RTUTF16)) + { + pwszSrcFree = RTUtf16Alloc((cwcSrc + 1) * sizeof(RTUTF16)); + if (pwszSrcFree) + { + memcpy(pwszSrcFree, pwszSrc, cwcSrc * sizeof(RTUTF16)); + pwszSrcFree[cwcSrc] = '\0'; + pwszSrc = pwszSrcFree; + } + else + { + rc = VERR_NO_UTF16_MEMORY; + pwszSrc = NULL; + } + } + } + else + rc = VERR_GENERAL_FAILURE; + } + /* Second preference is plain UTF-8 text: */ + else + { + orc = PasteboardCopyItemFlavorData(pPasteboard, idItem, kUTTypeUTF8PlainText, &hDataCopy); + if (orc == 0) + { + cbDataCopy = CFDataGetLength(hDataCopy); + Log(("readFromPasteboard: clipboard content is utf-8 (%zu bytes)\n", cbDataCopy)); + const char *pszSrc = (const char *)CFDataGetBytePtr(hDataCopy); + if (pszSrc) + { + size_t cchSrc = RTStrNLen(pszSrc, cbDataCopy); + rc = RTStrToUtf16Ex(pszSrc, cchSrc, &pwszSrcFree, 0, &cwcSrc); + if (RT_SUCCESS(rc)) + pwszSrc = pwszSrcFree; + } + else + rc = VERR_GENERAL_FAILURE; + } + } + if (pwszSrc) + { + /* + * Convert to windows UTF-16. + */ + Assert(cwcSrc == RTUtf16Len(pwszSrc)); + size_t cwcDst = 0; + rc = ShClUtf16LFLenUtf8(pwszSrc, cwcSrc, &cwcDst); + if (RT_SUCCESS(rc)) + { + cwcDst++; /* Add space for terminator. */ + + *pcbActual = cwcDst * sizeof(RTUTF16); + if (*pcbActual <= cb) + { + rc = ShClConvUtf16LFToCRLF(pwszSrc, cwcSrc, (PRTUTF16)pv, cb / sizeof(RTUTF16)); + if (RT_SUCCESS(rc)) + { +#ifdef SHOW_CLIPBOARD_CONTENT + Log(("readFromPasteboard: clipboard content: %ls\n", (PCRTUTF16)pv)); +#endif + } + else + { + Log(("readFromPasteboard: ShClUtf16LinToWin failed - %Rrc!\n", rc)); + AssertRC(rc); + } + } + else + { + Log(("readFromPasteboard: Insufficient (text) buffer space: %#zx, need %#zx\n", cb, *pcbActual)); + rc = VINF_SUCCESS; + } + } + else + { + Log(("readFromPasteboard: ShClUtf16GetWinSize failed - %Rrc!\n", rc)); + AssertRC(rc); + } + RTUtf16Free(pwszSrcFree); + } + } + /* + * The guest request BITMAP + */ + else if (fFormat & VBOX_SHCL_FMT_BITMAP) + { + /* Get the BMP data from the pasteboard */ + orc = PasteboardCopyItemFlavorData(pPasteboard, idItem, kUTTypeBMP, &hDataCopy); + if (orc == 0) + { + cbDataCopy = CFDataGetLength(hDataCopy); + Log(("Clipboard content is BMP (%zu bytes)\n", cbDataCopy)); + const void *pvSrc = CFDataGetBytePtr(hDataCopy); + if (pvSrc) + { + /* + * Try get the device independent bitmap (DIB) bit from it. + */ + const void *pvDib; + size_t cbDib; + rc = ShClBmpGetDib(pvSrc, cbDataCopy, &pvDib, &cbDib); + if (RT_SUCCESS(rc)) + { + *pcbActual = cbDib; + if (*pcbActual <= cb) + { + memcpy(pv, pvDib, cbDib); +#ifdef SHOW_CLIPBOARD_CONTENT + Log(("readFromPasteboard: clipboard content bitmap %zx bytes\n", cbDib)); +#endif + } + else + Log(("readFromPasteboard: Insufficient (bitmap) buffer space: %#zx, need %#zx\n", cb, cbDib)); + rc = VINF_SUCCESS; + } + else + { + AssertRC(rc); + Log(("readFromPasteboard: ShClBmpGetDib failed - %Rrc - unknown bitmap format??\n", rc)); + rc = VERR_NOT_SUPPORTED; + } + } + else + rc = VERR_GENERAL_FAILURE; + } + else + LogFlow(("readFromPasteboard: PasteboardCopyItemFlavorData/kUTTypeBMP -> %d (%#x)\n", orc, orc)); + } +#ifdef WITH_HTML_H2G + /* + * The guest request HTML. It expects a UTF-8 reply and we assume + * that's what's on the pasteboard too. + */ + else if (fFormat & VBOX_SHCL_FMT_HTML) + { + orc = PasteboardCopyItemFlavorData(pPasteboard, idItem, kUTTypeHTML, &hDataCopy); + if (orc == 0) + { + cbDataCopy = CFDataGetLength(hDataCopy); + Log(("Clipboard content is HTML (%zu bytes):\n", cbDataCopy)); + const char *pszSrc = (const char *)CFDataGetBytePtr(hDataCopy); + if (pszSrc) + { + Log3(("%.*Rhxd\n", cbDataCopy, pszSrc)); + rc = RTStrValidateEncodingEx(pszSrc, cbDataCopy, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + size_t cchSrc = RTStrNLen(pszSrc, cbDataCopy); + *pcbActual = cchSrc; + if (cchSrc <= cb) + memcpy(pv, pszSrc, cchSrc); + else + Log(("readFromPasteboard: Insufficient (HTML) buffer space: %#zx, need %#zx\n", cb, cchSrc)); + rc = VINF_SUCCESS; + } + else + { + Log(("readFromPasteboard: Invalid UTF-8 encoding on pasteboard: %Rrc\n", rc)); + rc = VERR_NOT_SUPPORTED; + } + } + else + rc = VERR_GENERAL_FAILURE; + } + else + LogFlow(("readFromPasteboard: PasteboardCopyItemFlavorData/kUTTypeHTML -> %d (%#x)\n", orc, orc)); + } +#endif + else + { + Log2(("readFromPasteboard: Unsupported format: %#x\n", fFormat)); + rc = VERR_NOT_SUPPORTED; + } + + /* + * Release the data copy, if we got one. There are no returns above! + */ + if (hDataCopy) + CFRelease(hDataCopy); + } + else + { + Log(("readFromPasteboard: PasteboardGetItemIdentifier failed: %u (%#x)\n", orc, orc)); + rc = VERR_NOT_SUPPORTED; + } + + Log(("readFromPasteboard: rc=%Rrc *pcbActual=%#zx\n", rc, *pcbActual)); + return rc; +} + +/** + * Takes the ownership of the pasteboard. + * + * This is called when the other end reports available formats. + * + * @returns VBox status code. + * @param hPasteboard The pastboard handle (reference). + * @param idOwnership The ownership ID to use now. + * @param pszOwnershipFlavor The ownership indicator flavor + * @param pszOwnershipValue The ownership value (stringified format mask). + * @param phStrOwnershipFlavor Pointer to a CFStringRef variable holding + * the current ownership flavor string. This + * will always be released, and set again on + * success. + * + * @todo Add fFormats so we can make promises about available formats at once + * without needing to request any data first. That might help on + * flavor priority. + */ +int takePasteboardOwnership(PasteboardRef hPasteboard, uint64_t idOwnership, const char *pszOwnershipFlavor, + const char *pszOwnershipValue, void **phStrOwnershipFlavor) +{ + /* + * Release the old string. + */ + if (*phStrOwnershipFlavor) + { + CFStringRef hOldFlavor = (CFStringRef)*phStrOwnershipFlavor; + CFRelease(hOldFlavor); + *phStrOwnershipFlavor = NULL; + } + + /* + * Clear the pasteboard and take ownership over it. + */ + OSStatus orc = PasteboardClear(hPasteboard); + if (orc == 0) + { + /* For good measure. */ + PasteboardSynchronize(hPasteboard); + + /* + * Put the ownership flavor and value onto the clipboard. + */ + CFDataRef hData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)pszOwnershipValue, strlen(pszOwnershipValue)); + if (hData) + { + CFStringRef hFlavor = CFStringCreateWithCString(kCFAllocatorDefault, pszOwnershipFlavor, kCFStringEncodingUTF8); + if (hFlavor) + { + orc = PasteboardPutItemFlavor(hPasteboard, (PasteboardItemID)idOwnership, + hFlavor, hData, kPasteboardFlavorNoFlags); + if (orc == 0) + { + *phStrOwnershipFlavor = (void *)hFlavor; + Log(("takePasteboardOwnership: idOwnership=%RX64 flavor=%s value=%s\n", + idOwnership, pszOwnershipFlavor, pszOwnershipValue)); + } + else + { + Log(("takePasteboardOwnership: PasteboardPutItemFlavor -> %d (%#x)!\n", orc, orc)); + CFRelease(hFlavor); + } + } + else + Log(("takePasteboardOwnership: CFStringCreateWithCString failed!\n")); + CFRelease(hData); + } + else + Log(("takePasteboardOwnership: CFDataCreate failed!\n")); + } + else + Log(("takePasteboardOwnership: PasteboardClear failed -> %d (%#x)\n", orc, orc)); + return orc == 0 ? VINF_SUCCESS : VERR_GENERAL_FAILURE; +} + +/** + * Write clipboard content to the host clipboard from the internal clipboard + * structure. + * + * @param hPasteboard Reference to the global pasteboard. + * @param idOwnership The ownership ID. + * @param pv The source buffer. + * @param cb The size of the source buffer. + * @param fFormat The format type which should be written. + * + * @returns IPRT status code. + */ +int writeToPasteboard(PasteboardRef hPasteboard, uint64_t idOwnership, const void *pv, uint32_t cb, uint32_t fFormat) +{ + int rc; + OSStatus orc; + CFDataRef hData; + Log(("writeToPasteboard: fFormat=%#x\n", fFormat)); + + /* Make sure all is in sync */ + PasteboardSynchronize(hPasteboard); + + /* + * Handle the unicode text + */ + if (fFormat & VBOX_SHCL_FMT_UNICODETEXT) + { + PCRTUTF16 const pwszSrc = (PCRTUTF16)pv; + size_t const cwcSrc = cb / sizeof(RTUTF16); + + /* + * If the other side is windows or OS/2, we may have to convert + * '\r\n' -> '\n' and the drop ending marker. + */ + + /* How long will the converted text be? */ + size_t cwcDst = 0; + rc = ShClUtf16CRLFLenUtf8(pwszSrc, cwcSrc, &cwcDst); + AssertMsgRCReturn(rc, ("ShClUtf16GetLinSize failed: %Rrc\n", rc), rc); + + /* Ignore empty strings? */ /** @todo r=andy Really? Why? */ + if (cwcDst == 0) + { + Log(("writeToPasteboard: received empty string from the guest; ignoreing it.\n")); + return VINF_SUCCESS; + } + + cwcDst++; /* Add space for terminator. */ + + /* Allocate the necessary memory and do the conversion. */ + PRTUTF16 pwszDst = (PRTUTF16)RTMemAlloc(cwcDst * sizeof(RTUTF16)); + AssertMsgReturn(pwszDst, ("cwcDst=%#zx\n", cwcDst), VERR_NO_UTF16_MEMORY); + + rc = ShClConvUtf16CRLFToLF(pwszSrc, cwcSrc, pwszDst, cwcDst); + if (RT_SUCCESS(rc)) + { + /* + * Create an immutable CFData object that we can place on the clipboard. + */ + hData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)pwszDst, cwcDst * sizeof(RTUTF16)); + if (hData) + { + orc = PasteboardPutItemFlavor(hPasteboard, (PasteboardItemID)idOwnership, + kUTTypeUTF16PlainText, hData, kPasteboardFlavorNoFlags); + if (orc == 0) + rc = VINF_SUCCESS; + else + { + Log(("writeToPasteboard: PasteboardPutItemFlavor/kUTTypeUTF16PlainText failed: %d (%#x)\n", orc, orc)); + rc = VERR_GENERAL_FAILURE; + } + CFRelease(hData); + } + else + { + Log(("writeToPasteboard: CFDataCreate/UTF16 failed!\n")); + rc = VERR_NO_MEMORY; + } + + /* + * Now for the UTF-8 version. + */ + char *pszDst; + int rc2 = RTUtf16ToUtf8(pwszDst, &pszDst); + if (RT_SUCCESS(rc2)) + { + hData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)pszDst, strlen(pszDst)); + if (hData) + { + orc = PasteboardPutItemFlavor(hPasteboard, (PasteboardItemID)idOwnership, + kUTTypeUTF8PlainText, hData, kPasteboardFlavorNoFlags); + if (orc != 0) + { + Log(("writeToPasteboard: PasteboardPutItemFlavor/kUTTypeUTF8PlainText failed: %d (%#x)\n", orc, orc)); + rc = VERR_GENERAL_FAILURE; + } + CFRelease(hData); + } + else + { + Log(("writeToPasteboard: CFDataCreate/UTF8 failed!\n")); + rc = VERR_NO_MEMORY; + } + RTStrFree(pszDst); + } + else + rc = rc2; + } + else + Log(("writeToPasteboard: clipboard conversion failed. vboxClipboardUtf16WinToLin() returned %Rrc. Abandoning.\n", rc)); + + RTMemFree(pwszDst); + } + /* + * Handle the bitmap. We convert the DIB to a bitmap and put it on + * the pasteboard using the BMP flavor. + */ + else if (fFormat & VBOX_SHCL_FMT_BITMAP) + { + /* Create a full BMP from it */ + void *pvBmp; + size_t cbBmp; + rc = ShClDibToBmp(pv, cb, &pvBmp, &cbBmp); + if (RT_SUCCESS(rc)) + { + hData = CFDataCreate(kCFAllocatorDefault, (UInt8 const *)pvBmp, cbBmp); + if (hData) + { + orc = PasteboardPutItemFlavor(hPasteboard, (PasteboardItemID)idOwnership, + kUTTypeBMP, hData, kPasteboardFlavorNoFlags); + if (orc != 0) + { + Log(("writeToPasteboard: PasteboardPutItemFlavor/kUTTypeBMP failed: %d (%#x)\n", orc, orc)); + rc = VERR_GENERAL_FAILURE; + } + CFRelease(hData); + } + else + { + Log(("writeToPasteboard: CFDataCreate/UTF8 failed!\n")); + rc = VERR_NO_MEMORY; + } + RTMemFree(pvBmp); + } + } +#ifdef WITH_HTML_G2H + /* + * Handle HTML. Expect UTF-8, ignore line endings and just put it + * straigh up on the pasteboard for now. + */ + else if (fFormat & VBOX_SHCL_FMT_HTML) + { + const char *pszSrc = (const char *)pv; + size_t const cchSrc = RTStrNLen(pszSrc, cb); + rc = RTStrValidateEncodingEx(pszSrc, cchSrc, 0); + if (RT_SUCCESS(rc)) + { + hData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)pszSrc, cchSrc); + if (hData) + { + orc = PasteboardPutItemFlavor(hPasteboard, (PasteboardItemID)idOwnership, kUTTypeHTML, + hData, kPasteboardFlavorNoFlags); + if (orc == 0) + rc = VINF_SUCCESS; + else + { + Log(("writeToPasteboard: PasteboardPutItemFlavor/kUTTypeHTML failed: %d (%#x)\n", orc, orc)); + rc = VERR_GENERAL_FAILURE; + } + CFRelease(hData); + } + else + { + Log(("writeToPasteboard: CFDataCreate/HTML failed!\n")); + rc = VERR_NO_MEMORY; + } + } + else + Log(("writeToPasteboard: HTML: Invalid UTF-8 encoding: %Rrc\n", rc)); + } +#endif + else + rc = VERR_NOT_IMPLEMENTED; + + Log(("writeToPasteboard: rc=%Rrc\n", rc)); + return rc; +} + diff --git a/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.h b/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.h new file mode 100644 index 00000000..38238d0c --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.h @@ -0,0 +1,47 @@ +/* $Id: darwin-pasteboard.h $ */ +/** @file + * Shared Clipboard Service - Mac OS X host implementation. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_SharedClipboard_darwin_pasteboard_h +#define VBOX_INCLUDED_SRC_SharedClipboard_darwin_pasteboard_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +typedef struct OpaquePasteboardRef *PasteboardRef; + +int initPasteboard(PasteboardRef *pPasteboardRef); +void destroyPasteboard(PasteboardRef *pPasteboardRef); + +int queryNewPasteboardFormats(PasteboardRef hPasteboard, uint64_t idOwnership, void *hStrOwnershipFlavor, + uint32_t *pfFormats, bool *pfChanged); +int readFromPasteboard(PasteboardRef pPasteboard, uint32_t fFormat, void *pv, uint32_t cb, uint32_t *pcbActual); +int takePasteboardOwnership(PasteboardRef pPasteboard, uint64_t idOwnership, const char *pszOwnershipFlavor, + const char *pszOwnershipValue, void **phStrOwnershipFlavor); +int writeToPasteboard(PasteboardRef hPasteboard, uint64_t idOwnership, void const *pv, uint32_t cb, uint32_t fFormat); + +#endif /* !VBOX_INCLUDED_SRC_SharedClipboard_darwin_pasteboard_h */ + diff --git a/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings b/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings new file mode 100644 index 00000000..9fe67d17 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings @@ -0,0 +1,29 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for the host HGCM services. +# + +# +# Copyright (C) 2019-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +--filter-out-files *.txt +/*.h: --guard-relative-to-dir . diff --git a/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk new file mode 100644 index 00000000..66160a26 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk @@ -0,0 +1,147 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Shared Clipboard Host Service testcases. +# + +# +# Copyright (C) 2011-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) + + # + # Testcase which mocks HGCM to also test the VbglR3-side of Shared Clipboard. + # + # Goal is to use and test as much guest side code as possible as a self-contained + # binary on the host here. + # + # Note: No #ifdef TESTCASE hacks or similar allowed, has to run + # without #ifdef modifications to the core code! + # + tstClipboardMockHGCM_TEMPLATE = VBoxR3TstExe + tstClipboardMockHGCM_DEFS = VBOX_WITH_HGCM VBOX_WITH_SHARED_CLIPBOARD + tstClipboardMockHGCM_SOURCES = \ + ../VBoxSharedClipboardSvc.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \ + tstClipboardMockHGCM.cpp + tstClipboardMockHGCM_LIBS = $(LIB_RUNTIME) + + if1of ($(KBUILD_TARGET), linux solaris) + PROGRAMS += tstClipboardMockHGCM + tstClipboardMockHGCM_SOURCES += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp \ + ../VBoxSharedClipboardSvc-x11.cpp + tstClipboardMockHGCM_LIBPATH = \ + $(VBOX_LIBPATH_X11) + tstClipboardMockHGCM_LIBS += \ + Xt \ + X11 + endif + if1of ($(KBUILD_TARGET), win) + PROGRAMS += tstClipboardMockHGCM + tstClipboardMockHGCM_SOURCES += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp \ + ../VBoxSharedClipboardSvc-win.cpp + endif + + tstClipboardMockHGCM_CLEAN = $(tstClipboardMockHGCM_0_OUTDIR)/tstClipboardMockHGCM.run + + if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) + tstClipboardMockHGCM_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardMockHGCM_SOURCES += \ + ../VBoxSharedClipboardSvc-transfers.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp + endif + + if 0 # Enable this if you want automatic runs after compilation. + $$(tstClipboardMockHGCM_0_OUTDIR)/tstClipboardMockHGCM.run: $$(tstClipboardMockHGCM_1_STAGE_TARGET) + export VBOX_LOG_DEST=nofile; $(tstClipboardMockHGCM_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + OTHERS += $(tstClipboardMockHGCM_0_OUTDIR)/tstClipboardMockHGCM.run + endif + + # + # + # + PROGRAMS += tstClipboardServiceHost + tstClipboardServiceHost_TEMPLATE = VBoxR3TstExe + tstClipboardServiceHost_DEFS = VBOX_WITH_HGCM UNIT_TEST + tstClipboardServiceHost_SOURCES = \ + ../VBoxSharedClipboardSvc.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \ + tstClipboardServiceHost.cpp + tstClipboardServiceHost_LIBS = $(LIB_RUNTIME) + tstClipboardServiceHost_CLEAN = $(tstClipboardServiceHost_0_OUTDIR)/tstClipboardServiceHost.run + + if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) + tstClipboardServiceHost_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardServiceHost_SOURCES += \ + ../VBoxSharedClipboardSvc-transfers.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp + endif + + # + # + # + PROGRAMS += tstClipboardServiceImpl + tstClipboardServiceImpl_TEMPLATE = VBoxR3TstExe + tstClipboardServiceImpl_DEFS = VBOX_WITH_HGCM UNIT_TEST + tstClipboardServiceImpl_SOURCES = \ + ../VBoxSharedClipboardSvc.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \ + tstClipboardServiceImpl.cpp + tstClipboardServiceImpl_SOURCES.win = \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp + tstClipboardServiceImpl_LIBS = $(LIB_RUNTIME) + tstClipboardServiceImpl_CLEAN = $(tstClipboardServiceImpl_0_OUTDIR)/tstClipboardServiceImpl.run + + if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) + # + # + # + PROGRAMS += tstClipboardTransfers + tstClipboardTransfers_TEMPLATE = VBoxR3TstExe + tstClipboardTransfers_DEFS = VBOX_WITH_HGCM UNIT_TEST VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardTransfers_SOURCES = \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \ + tstClipboardTransfers.cpp + endif +endif + +# +# List of above testcases that will be included in the ValKit. +# +ifdef VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING + if1of ($(KBUILD_TARGET), linux solaris win) + VALKIT_UNITTESTS_WHITELIST_GUEST_ADDITIONS += \ + tstClipboardMockHGCM + endif +endif # VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h new file mode 100644 index 00000000..35791604 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h @@ -0,0 +1,165 @@ +/* $Id: VBoxOrgCfHtml1.h $ */ +/** @file + * Shared Clipboard host service test case C data file of VBoxOrgCfHtml1.txt. + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxOrgCfHtml1_h +#define VBOX_INCLUDED_SRC_VBoxOrgCfHtml1_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> + +const unsigned char g_abVBoxOrgCfHtml1[] = +{ + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x30, 0x2e, 0x39, 0x0d, 0x0a, 0x53, 0x74, 0x61, /* 0x00000000: Version:0.9..Sta */ + 0x72, 0x74, 0x48, 0x54, 0x4d, 0x4c, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x34, /* 0x00000010: rtHTML:000000014 */ + 0x34, 0x0d, 0x0a, 0x45, 0x6e, 0x64, 0x48, 0x54, 0x4d, 0x4c, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, /* 0x00000020: 4..EndHTML:00000 */ + 0x30, 0x31, 0x39, 0x36, 0x31, 0x0d, 0x0a, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x72, 0x61, 0x67, /* 0x00000030: 01961..StartFrag */ + 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x38, 0x30, 0x0d, /* 0x00000040: ment:0000000180. */ + 0x0a, 0x45, 0x6e, 0x64, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x30, 0x30, 0x30, /* 0x00000050: .EndFragment:000 */ + 0x30, 0x30, 0x30, 0x31, 0x39, 0x32, 0x35, 0x0d, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, /* 0x00000060: 0001925..SourceU */ + 0x52, 0x4c, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, /* 0x00000070: RL:https:..www.v */ + 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x62, 0x6f, 0x78, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x0d, 0x0a, /* 0x00000080: irtualbox.org... */ + 0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x0d, 0x0a, 0x3c, 0x62, 0x6f, 0x64, 0x79, 0x3e, 0x0d, 0x0a, /* 0x00000090: <html>..<body>.. */ + 0x3c, 0x21, 0x2d, 0x2d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, /* 0x000000a0: <!--StartFragmen */ + 0x74, 0x2d, 0x2d, 0x3e, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, /* 0x000000b0: t--><span style= */ + 0x22, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x30, 0x2c, 0x20, 0x30, /* 0x000000c0: "color: rgb(0, 0 */ + 0x2c, 0x20, 0x30, 0x29, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, /* 0x000000d0: , 0); font-famil */ + 0x79, 0x3a, 0x20, 0x56, 0x65, 0x72, 0x64, 0x61, 0x6e, 0x61, 0x2c, 0x20, 0x26, 0x71, 0x75, 0x6f, /* 0x000000e0: y: Verdana, &quo */ + 0x74, 0x3b, 0x42, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x56, 0x65, 0x72, 0x61, /* 0x000000f0: t;Bitstream Vera */ + 0x20, 0x53, 0x61, 0x6e, 0x73, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x2c, 0x20, 0x73, 0x61, 0x6e, /* 0x00000100: Sans", san */ + 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, /* 0x00000110: s-serif; font-si */ + 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x33, 0x70, 0x78, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, /* 0x00000120: ze: 13px; font-s */ + 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, /* 0x00000130: tyle: normal; fo */ + 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x6c, 0x69, 0x67, 0x61, 0x74, /* 0x00000140: nt-variant-ligat */ + 0x75, 0x72, 0x65, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, /* 0x00000150: ures: normal; fo */ + 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x63, 0x61, 0x70, 0x73, 0x3a, /* 0x00000160: nt-variant-caps: */ + 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, /* 0x00000170: normal; font-we */ + 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30, 0x30, 0x3b, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, /* 0x00000180: ight: 400; lette */ + 0x72, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, /* 0x00000190: r-spacing: norma */ + 0x6c, 0x3b, 0x20, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x74, /* 0x000001a0: l; orphans: 2; t */ + 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, /* 0x000001b0: ext-align: start */ + 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x30, /* 0x000001c0: ; text-indent: 0 */ + 0x70, 0x78, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, /* 0x000001d0: px; text-transfo */ + 0x72, 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, /* 0x000001e0: rm: none; white- */ + 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x77, /* 0x000001f0: space: normal; w */ + 0x69, 0x64, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x73, /* 0x00000200: idows: 2; word-s */ + 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x2d, 0x77, 0x65, /* 0x00000210: pacing: 0px; -we */ + 0x62, 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, /* 0x00000220: bkit-text-stroke */ + 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x62, 0x61, 0x63, /* 0x00000230: -width: 0px; bac */ + 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, /* 0x00000240: kground-color: r */ + 0x67, 0x62, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, /* 0x00000250: gb(255, 255, 255 */ + 0x29, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, /* 0x00000260: ); text-decorati */ + 0x6f, 0x6e, 0x2d, 0x74, 0x68, 0x69, 0x63, 0x6b, 0x6e, 0x65, 0x73, 0x73, 0x3a, 0x20, 0x69, 0x6e, /* 0x00000270: on-thickness: in */ + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, /* 0x00000280: itial; text-deco */ + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x69, 0x6e, /* 0x00000290: ration-style: in */ + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, /* 0x000002a0: itial; text-deco */ + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x69, 0x6e, /* 0x000002b0: ration-color: in */ + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, /* 0x000002c0: itial; display: */ + 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x21, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, /* 0x000002d0: inline !importan */ + 0x74, 0x3b, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x22, /* 0x000002e0: t; float: none;" */ + 0x3e, 0x53, 0x65, 0x65, 0x20, 0x22, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x3c, 0x61, 0x20, /* 0x000002f0: >See "<.span><a */ + 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x77, 0x69, 0x6b, 0x69, 0x22, 0x20, 0x68, 0x72, 0x65, /* 0x00000300: class="wiki" hre */ + 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, /* 0x00000310: f="https:..www.v */ + 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x62, 0x6f, 0x78, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x77, 0x69, /* 0x00000320: irtualbox.org.wi */ + 0x6b, 0x69, 0x2f, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x42, 0x6f, 0x78, 0x22, 0x20, 0x73, /* 0x00000330: ki.VirtualBox" s */ + 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, /* 0x00000340: tyle="text-decor */ + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x63, 0x6f, 0x6c, /* 0x00000350: ation: none; col */ + 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x31, 0x39, /* 0x00000360: or: rgb(0, 0, 19 */ + 0x32, 0x29, 0x3b, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, /* 0x00000370: 2); border-botto */ + 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, /* 0x00000380: m: none; font-fa */ + 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x56, 0x65, 0x72, 0x64, 0x61, 0x6e, 0x61, 0x2c, 0x20, 0x26, /* 0x00000390: mily: Verdana, & */ + 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x42, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x56, /* 0x000003a0: quot;Bitstream V */ + 0x65, 0x72, 0x61, 0x20, 0x53, 0x61, 0x6e, 0x73, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x2c, 0x20, /* 0x000003b0: era Sans", */ + 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, /* 0x000003c0: sans-serif; font */ + 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x33, 0x70, 0x78, 0x3b, 0x20, 0x66, 0x6f, 0x6e, /* 0x000003d0: -size: 13px; fon */ + 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, /* 0x000003e0: t-style: normal; */ + 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x6c, 0x69, /* 0x000003f0: font-variant-li */ + 0x67, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, /* 0x00000400: gatures: normal; */ + 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x63, 0x61, /* 0x00000410: font-variant-ca */ + 0x70, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, /* 0x00000420: ps: normal; font */ + 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30, 0x30, 0x3b, 0x20, 0x6c, 0x65, /* 0x00000430: -weight: 400; le */ + 0x74, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x6e, 0x6f, /* 0x00000440: tter-spacing: no */ + 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x32, /* 0x00000450: rmal; orphans: 2 */ + 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x73, 0x74, /* 0x00000460: ; text-align: st */ + 0x61, 0x72, 0x74, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x6e, 0x74, /* 0x00000470: art; text-indent */ + 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, /* 0x00000480: : 0px; text-tran */ + 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x77, 0x68, 0x69, /* 0x00000490: sform: none; whi */ + 0x74, 0x65, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, /* 0x000004a0: te-space: normal */ + 0x3b, 0x20, 0x77, 0x69, 0x64, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x77, 0x6f, 0x72, /* 0x000004b0: ; widows: 2; wor */ + 0x64, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, /* 0x000004c0: d-spacing: 0px; */ + 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x73, 0x74, 0x72, /* 0x000004d0: -webkit-text-str */ + 0x6f, 0x6b, 0x65, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, /* 0x000004e0: oke-width: 0px; */ + 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, /* 0x000004f0: background-color */ + 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, /* 0x00000500: : rgb(255, 255, */ + 0x32, 0x35, 0x35, 0x29, 0x3b, 0x22, 0x3e, 0x41, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x56, 0x69, 0x72, /* 0x00000510: 255);">About Vir */ + 0x74, 0x75, 0x61, 0x6c, 0x42, 0x6f, 0x78, 0x3c, 0x2f, 0x61, 0x3e, 0x3c, 0x73, 0x70, 0x61, 0x6e, /* 0x00000520: tualBox<.a><span */ + 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, /* 0x00000530: style="color: r */ + 0x67, 0x62, 0x28, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x29, 0x3b, 0x20, 0x66, 0x6f, 0x6e, /* 0x00000540: gb(0, 0, 0); fon */ + 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x56, 0x65, 0x72, 0x64, 0x61, 0x6e, /* 0x00000550: t-family: Verdan */ + 0x61, 0x2c, 0x20, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x42, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, /* 0x00000560: a, "Bitstre */ + 0x61, 0x6d, 0x20, 0x56, 0x65, 0x72, 0x61, 0x20, 0x53, 0x61, 0x6e, 0x73, 0x26, 0x71, 0x75, 0x6f, /* 0x00000570: am Vera Sans&quo */ + 0x74, 0x3b, 0x2c, 0x20, 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x3b, 0x20, /* 0x00000580: t;, sans-serif; */ + 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x33, 0x70, 0x78, 0x3b, /* 0x00000590: font-size: 13px; */ + 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, /* 0x000005a0: font-style: nor */ + 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, /* 0x000005b0: mal; font-varian */ + 0x74, 0x2d, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, /* 0x000005c0: t-ligatures: nor */ + 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, /* 0x000005d0: mal; font-varian */ + 0x74, 0x2d, 0x63, 0x61, 0x70, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, /* 0x000005e0: t-caps: normal; */ + 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30, 0x30, /* 0x000005f0: font-weight: 400 */ + 0x3b, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, /* 0x00000600: ; letter-spacing */ + 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, /* 0x00000610: : normal; orphan */ + 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, /* 0x00000620: s: 2; text-align */ + 0x3a, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x69, 0x6e, /* 0x00000630: : start; text-in */ + 0x64, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, /* 0x00000640: dent: 0px; text- */ + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, /* 0x00000650: transform: none; */ + 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, /* 0x00000660: white-space: no */ + 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x77, 0x69, 0x64, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x3b, /* 0x00000670: rmal; widows: 2; */ + 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, /* 0x00000680: word-spacing: 0 */ + 0x70, 0x78, 0x3b, 0x20, 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x65, 0x78, 0x74, /* 0x00000690: px; -webkit-text */ + 0x2d, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, /* 0x000006a0: -stroke-width: 0 */ + 0x70, 0x78, 0x3b, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, /* 0x000006b0: px; background-c */ + 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, /* 0x000006c0: olor: rgb(255, 2 */ + 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, /* 0x000006d0: 55, 255); text-d */ + 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x74, 0x68, 0x69, 0x63, 0x6b, 0x6e, /* 0x000006e0: ecoration-thickn */ + 0x65, 0x73, 0x73, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, /* 0x000006f0: ess: initial; te */ + 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x73, 0x74, /* 0x00000700: xt-decoration-st */ + 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, /* 0x00000710: yle: initial; te */ + 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, /* 0x00000720: xt-decoration-co */ + 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x64, 0x69, /* 0x00000730: lor: initial; di */ + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x21, 0x69, /* 0x00000740: splay: inline !i */ + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x3b, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, /* 0x00000750: mportant; float: */ + 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x22, 0x3e, 0x22, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6e, /* 0x00000760: none;">" for an */ + 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x3c, 0x2f, /* 0x00000770: introduction.<. */ + 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x3c, 0x21, 0x2d, 0x2d, 0x45, 0x6e, 0x64, 0x46, 0x72, 0x61, 0x67, /* 0x00000780: span><!--EndFrag */ + 0x6d, 0x65, 0x6e, 0x74, 0x2d, 0x2d, 0x3e, 0x0d, 0x0a, 0x3c, 0x2f, 0x62, 0x6f, 0x64, 0x79, 0x3e, /* 0x00000790: ment-->..<.body> */ + 0x0d, 0x0a, 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x00, /* 0x000007a0: ..<.html>. */ +}; + +const unsigned g_cbVBoxOrgCfHtml1 = sizeof(g_abVBoxOrgCfHtml1); + +#endif /* !VBOX_INCLUDED_SRC_VBoxOrgCfHtml1_h */ diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txt b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txt Binary files differnew file mode 100644 index 00000000..39a83822 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txt diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h new file mode 100644 index 00000000..a2a0fe41 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h @@ -0,0 +1,152 @@ +/* $Id: VBoxOrgMimeHtml1.h $ */ +/** @file + * Shared Clipboard host service test case C data file of VBoxOrgMimeHtml1.txt. + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxOrgMimeHtml1_h +#define VBOX_INCLUDED_SRC_VBoxOrgMimeHtml1_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> + +const unsigned char g_abVBoxOrgMimeHtml1[] = +{ + 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x63, 0x6f, 0x6c, /* 0x00000000: <span style="col */ + 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x29, /* 0x00000010: or: rgb(0, 0, 0) */ + 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x56, /* 0x00000020: ; font-family: V */ + 0x65, 0x72, 0x64, 0x61, 0x6e, 0x61, 0x2c, 0x20, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x42, 0x69, /* 0x00000030: erdana, "Bi */ + 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x56, 0x65, 0x72, 0x61, 0x20, 0x53, 0x61, 0x6e, /* 0x00000040: tstream Vera San */ + 0x73, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x2c, 0x20, 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, /* 0x00000050: s", sans-se */ + 0x72, 0x69, 0x66, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, /* 0x00000060: rif; font-size: */ + 0x31, 0x33, 0x70, 0x78, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, /* 0x00000070: 13px; font-style */ + 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, /* 0x00000080: : normal; font-v */ + 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, /* 0x00000090: ariant-ligatures */ + 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, /* 0x000000a0: : normal; font-v */ + 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x63, 0x61, 0x70, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, /* 0x000000b0: ariant-caps: nor */ + 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, /* 0x000000c0: mal; font-weight */ + 0x3a, 0x20, 0x34, 0x30, 0x30, 0x3b, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x70, /* 0x000000d0: : 400; letter-sp */ + 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x6f, /* 0x000000e0: acing: normal; o */ + 0x72, 0x70, 0x68, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, /* 0x000000f0: rphans: 2; text- */ + 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x3b, 0x20, 0x74, 0x65, /* 0x00000100: align: start; te */ + 0x78, 0x74, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, /* 0x00000110: xt-indent: 0px; */ + 0x74, 0x65, 0x78, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, /* 0x00000120: text-transform: */ + 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, 0x73, 0x70, 0x61, 0x63, /* 0x00000130: none; white-spac */ + 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x77, 0x69, 0x64, 0x6f, 0x77, /* 0x00000140: e: normal; widow */ + 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, /* 0x00000150: s: 2; word-spaci */ + 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, /* 0x00000160: ng: 0px; -webkit */ + 0x2d, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, 0x2d, 0x77, 0x69, 0x64, /* 0x00000170: -text-stroke-wid */ + 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, /* 0x00000180: th: 0px; backgro */ + 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x32, /* 0x00000190: und-color: rgb(2 */ + 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, 0x3b, 0x20, 0x74, /* 0x000001a0: 55, 255, 255); t */ + 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x74, /* 0x000001b0: ext-decoration-t */ + 0x68, 0x69, 0x63, 0x6b, 0x6e, 0x65, 0x73, 0x73, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, /* 0x000001c0: hickness: initia */ + 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, /* 0x000001d0: l; text-decorati */ + 0x6f, 0x6e, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, /* 0x000001e0: on-style: initia */ + 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, /* 0x000001f0: l; text-decorati */ + 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, /* 0x00000200: on-color: initia */ + 0x6c, 0x3b, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69, /* 0x00000210: l; display: inli */ + 0x6e, 0x65, 0x20, 0x21, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x3b, 0x20, 0x66, /* 0x00000220: ne !important; f */ + 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x22, 0x3e, 0x53, 0x65, 0x65, /* 0x00000230: loat: none;">See */ + 0x20, 0x22, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x3c, 0x61, 0x20, 0x63, 0x6c, 0x61, 0x73, /* 0x00000240: "<.span><a clas */ + 0x73, 0x3d, 0x22, 0x77, 0x69, 0x6b, 0x69, 0x22, 0x20, 0x68, 0x72, 0x65, 0x66, 0x3d, 0x22, 0x68, /* 0x00000250: s="wiki" href="h */ + 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x69, 0x72, 0x74, 0x75, /* 0x00000260: ttps:..www.virtu */ + 0x61, 0x6c, 0x62, 0x6f, 0x78, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x77, 0x69, 0x6b, 0x69, 0x2f, 0x56, /* 0x00000270: albox.org.wiki.V */ + 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x42, 0x6f, 0x78, 0x22, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, /* 0x00000280: irtualBox" style */ + 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, /* 0x00000290: ="text-decoratio */ + 0x6e, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, /* 0x000002a0: n: none; color: */ + 0x72, 0x67, 0x62, 0x28, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x31, 0x39, 0x32, 0x29, 0x3b, 0x20, /* 0x000002b0: rgb(0, 0, 192); */ + 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x6e, /* 0x000002c0: border-bottom: n */ + 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, /* 0x000002d0: one; font-family */ + 0x3a, 0x20, 0x56, 0x65, 0x72, 0x64, 0x61, 0x6e, 0x61, 0x2c, 0x20, 0x26, 0x71, 0x75, 0x6f, 0x74, /* 0x000002e0: : Verdana, " */ + 0x3b, 0x42, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x56, 0x65, 0x72, 0x61, 0x20, /* 0x000002f0: ;Bitstream Vera */ + 0x53, 0x61, 0x6e, 0x73, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x2c, 0x20, 0x73, 0x61, 0x6e, 0x73, /* 0x00000300: Sans", sans */ + 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, /* 0x00000310: -serif; font-siz */ + 0x65, 0x3a, 0x20, 0x31, 0x33, 0x70, 0x78, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x74, /* 0x00000320: e: 13px; font-st */ + 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, /* 0x00000330: yle: normal; fon */ + 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x75, /* 0x00000340: t-variant-ligatu */ + 0x72, 0x65, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, /* 0x00000350: res: normal; fon */ + 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x63, 0x61, 0x70, 0x73, 0x3a, 0x20, /* 0x00000360: t-variant-caps: */ + 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x69, /* 0x00000370: normal; font-wei */ + 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30, 0x30, 0x3b, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, /* 0x00000380: ght: 400; letter */ + 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, /* 0x00000390: -spacing: normal */ + 0x3b, 0x20, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x74, 0x65, /* 0x000003a0: ; orphans: 2; te */ + 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x3b, /* 0x000003b0: xt-align: start; */ + 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x70, /* 0x000003c0: text-indent: 0p */ + 0x78, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, /* 0x000003d0: x; text-transfor */ + 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, 0x73, /* 0x000003e0: m: none; white-s */ + 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x77, 0x69, /* 0x000003f0: pace: normal; wi */ + 0x64, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x73, 0x70, /* 0x00000400: dows: 2; word-sp */ + 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x2d, 0x77, 0x65, 0x62, /* 0x00000410: acing: 0px; -web */ + 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, 0x2d, /* 0x00000420: kit-text-stroke- */ + 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x62, 0x61, 0x63, 0x6b, /* 0x00000430: width: 0px; back */ + 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, /* 0x00000440: ground-color: rg */ + 0x62, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, /* 0x00000450: b(255, 255, 255) */ + 0x3b, 0x22, 0x3e, 0x41, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, /* 0x00000460: ;">About Virtual */ + 0x42, 0x6f, 0x78, 0x3c, 0x2f, 0x61, 0x3e, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, /* 0x00000470: Box<.a><span sty */ + 0x6c, 0x65, 0x3d, 0x22, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x30, /* 0x00000480: le="color: rgb(0 */ + 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x29, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, /* 0x00000490: , 0, 0); font-fa */ + 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x56, 0x65, 0x72, 0x64, 0x61, 0x6e, 0x61, 0x2c, 0x20, 0x26, /* 0x000004a0: mily: Verdana, & */ + 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x42, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x56, /* 0x000004b0: quot;Bitstream V */ + 0x65, 0x72, 0x61, 0x20, 0x53, 0x61, 0x6e, 0x73, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x2c, 0x20, /* 0x000004c0: era Sans", */ + 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, /* 0x000004d0: sans-serif; font */ + 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x33, 0x70, 0x78, 0x3b, 0x20, 0x66, 0x6f, 0x6e, /* 0x000004e0: -size: 13px; fon */ + 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, /* 0x000004f0: t-style: normal; */ + 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x6c, 0x69, /* 0x00000500: font-variant-li */ + 0x67, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, /* 0x00000510: gatures: normal; */ + 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x63, 0x61, /* 0x00000520: font-variant-ca */ + 0x70, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, /* 0x00000530: ps: normal; font */ + 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30, 0x30, 0x3b, 0x20, 0x6c, 0x65, /* 0x00000540: -weight: 400; le */ + 0x74, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x6e, 0x6f, /* 0x00000550: tter-spacing: no */ + 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x32, /* 0x00000560: rmal; orphans: 2 */ + 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x73, 0x74, /* 0x00000570: ; text-align: st */ + 0x61, 0x72, 0x74, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x6e, 0x74, /* 0x00000580: art; text-indent */ + 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, /* 0x00000590: : 0px; text-tran */ + 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x77, 0x68, 0x69, /* 0x000005a0: sform: none; whi */ + 0x74, 0x65, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, /* 0x000005b0: te-space: normal */ + 0x3b, 0x20, 0x77, 0x69, 0x64, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x77, 0x6f, 0x72, /* 0x000005c0: ; widows: 2; wor */ + 0x64, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, /* 0x000005d0: d-spacing: 0px; */ + 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x73, 0x74, 0x72, /* 0x000005e0: -webkit-text-str */ + 0x6f, 0x6b, 0x65, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, /* 0x000005f0: oke-width: 0px; */ + 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, /* 0x00000600: background-color */ + 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, /* 0x00000610: : rgb(255, 255, */ + 0x32, 0x35, 0x35, 0x29, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, /* 0x00000620: 255); text-decor */ + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x74, 0x68, 0x69, 0x63, 0x6b, 0x6e, 0x65, 0x73, 0x73, 0x3a, /* 0x00000630: ation-thickness: */ + 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, /* 0x00000640: initial; text-d */ + 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, /* 0x00000650: ecoration-style: */ + 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, /* 0x00000660: initial; text-d */ + 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, /* 0x00000670: ecoration-color: */ + 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, /* 0x00000680: initial; displa */ + 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x21, 0x69, 0x6d, 0x70, 0x6f, 0x72, /* 0x00000690: y: inline !impor */ + 0x74, 0x61, 0x6e, 0x74, 0x3b, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, /* 0x000006a0: tant; float: non */ + 0x65, 0x3b, 0x22, 0x3e, 0x22, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x20, 0x69, 0x6e, 0x74, /* 0x000006b0: e;">" for an int */ + 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, /* 0x000006c0: roduction.<.span */ + 0x3e, 0x00 /* 0x000006d0: > */ +}; + +const unsigned g_cbVBoxOrgMimeHtml1 = sizeof(g_abVBoxOrgMimeHtml1); + +#endif /* !VBOX_INCLUDED_SRC_VBoxOrgMimeHtml1_h */ diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt new file mode 100644 index 00000000..6d5b4901 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt @@ -0,0 +1 @@ +<span style="color: rgb(0, 0, 0); font-family: Verdana, "Bitstream Vera Sans", sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">See "</span><a class="wiki" href="https://www.virtualbox.org/wiki/VirtualBox" style="text-decoration: none; color: rgb(0, 0, 192); border-bottom: none; font-family: Verdana, "Bitstream Vera Sans", sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255);">About VirtualBox</a><span style="color: rgb(0, 0, 0); font-family: Verdana, "Bitstream Vera Sans", sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">" for an introduction.</span>
\ No newline at end of file diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp new file mode 100644 index 00000000..ec35905c --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp @@ -0,0 +1,735 @@ +/* $Id: tstClipboardMockHGCM.cpp $ */ +/** @file + * Shared Clipboard host service test case. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "../VBoxSharedClipboardSvc-internal.h" + +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <VBox/VBoxGuestLib.h> +#ifdef RT_OS_LINUX +# include <VBox/GuestHost/SharedClipboard-x11.h> +#endif +#ifdef RT_OS_WINDOWS +# include <VBox/GuestHost/SharedClipboard-win.h> +#endif + +#include <VBox/GuestHost/HGCMMock.h> +#include <VBox/GuestHost/HGCMMockUtils.h> + +#include <iprt/assert.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/rand.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/test.h> +#include <iprt/utf16.h> + + +/********************************************************************************************************************************* +* Static globals * +*********************************************************************************************************************************/ +static RTTEST g_hTest; + + +/********************************************************************************************************************************* +* Shared Clipboard testing * +*********************************************************************************************************************************/ +struct CLIPBOARDTESTDESC; +/** Pointer to a test description. */ +typedef CLIPBOARDTESTDESC *PTESTDESC; + +struct CLIPBOARDTESTCTX; +/** Pointer to a test context. */ +typedef CLIPBOARDTESTCTX *PCLIPBOARDTESTCTX; + +/** Pointer a test descriptor. */ +typedef CLIPBOARDTESTDESC *PTESTDESC; + +typedef DECLCALLBACKTYPE(int, FNTESTSETUP,(PCLIPBOARDTESTCTX pTstCtx, void **ppvCtx)); +/** Pointer to a test setup callback. */ +typedef FNTESTSETUP *PFNTESTSETUP; + +typedef DECLCALLBACKTYPE(int, FNTESTEXEC,(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx)); +/** Pointer to a test exec callback. */ +typedef FNTESTEXEC *PFNTESTEXEC; + +typedef DECLCALLBACKTYPE(int, FNTESTDESTROY,(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx)); +/** Pointer to a test destroy callback. */ +typedef FNTESTDESTROY *PFNTESTDESTROY; + + +/** + * Structure for keeping a clipboard test task. + */ +typedef struct CLIPBOARDTESTTASK +{ + SHCLFORMATS enmFmtHst; + SHCLFORMATS enmFmtGst; + /** For testing chunked reads / writes. */ + size_t cbChunk; + /** Data buffer to read / write for this task. + * Can be NULL if not needed. */ + void *pvData; + /** Size (in bytes) of \a pvData. */ + size_t cbData; + /** Number of bytes read / written from / to \a pvData. */ + size_t cbProcessed; +} CLIPBOARDTESTTASK; +typedef CLIPBOARDTESTTASK *PCLIPBOARDTESTTASK; + +/** + * Structure for keeping a clipboard test context. + */ +typedef struct CLIPBOARDTESTCTX +{ + /** The HGCM Mock utils context. */ + TSTHGCMUTILSCTX HGCM; + /** Clipboard-specific task data. */ + CLIPBOARDTESTTASK Task; + struct + { + /** The VbglR3 Shared Clipboard context to work on. */ + VBGLR3SHCLCMDCTX CmdCtx; + } Guest; +} CLIPBOARDTESTCTX; + +/** The one and only clipboard test context. One at a time. */ +CLIPBOARDTESTCTX g_TstCtx; + +/** + * Structure for keeping a clipboard test description. + */ +typedef struct CLIPBOARDTESTDESC +{ + /** The setup callback. */ + PFNTESTSETUP pfnSetup; + /** The exec callback. */ + PFNTESTEXEC pfnExec; + /** The destruction callback. */ + PFNTESTDESTROY pfnDestroy; +} CLIPBOARDTESTDESC; + +typedef struct SHCLCONTEXT +{ +} SHCLCONTEXT; + + +static int tstSetModeRc(PTSTHGCMMOCKSVC pSvc, uint32_t uMode, int rcExpected) +{ + VBOXHGCMSVCPARM aParms[2]; + HGCMSvcSetU32(&aParms[0], uMode); + int rc2 = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, aParms); + RTTESTI_CHECK_MSG_RET(rcExpected == rc2, ("Expected %Rrc, got %Rrc\n", rcExpected, rc2), rc2); + if (RT_SUCCESS(rcExpected)) + { + uint32_t const uModeRet = ShClSvcGetMode(); + RTTESTI_CHECK_MSG_RET(uMode == uModeRet, ("Expected mode %RU32, got %RU32\n", uMode, uModeRet), VERR_WRONG_TYPE); + } + return rc2; +} + +static int tstClipboardSetMode(PTSTHGCMMOCKSVC pSvc, uint32_t uMode) +{ + return tstSetModeRc(pSvc, uMode, VINF_SUCCESS); +} + +static bool tstClipboardGetMode(PTSTHGCMMOCKSVC pSvc, uint32_t uModeExpected) +{ + RT_NOREF(pSvc); + RTTESTI_CHECK_RET(ShClSvcGetMode() == uModeExpected, false); + return true; +} + +static void tstOperationModes(void) +{ + struct VBOXHGCMSVCPARM parms[2]; + uint32_t u32Mode; + int rc; + + RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_MODE"); + + PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); + + /* Reset global variable which doesn't reset itself. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_OFF); + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + u32Mode = ShClSvcGetMode(); + RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode)); + + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 0, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 2, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + HGCMSvcSetU64(&parms[0], 99); + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_HOST_TO_GUEST); + tstSetModeRc(pSvc, 99, VERR_NOT_SUPPORTED); + tstClipboardGetMode(pSvc, VBOX_SHCL_MODE_OFF); +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +static void testSetTransferMode(void) +{ + RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE"); + + PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); + + /* Invalid parameter. */ + VBOXHGCMSVCPARM parms[2]; + HGCMSvcSetU64(&parms[0], 99); + int rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + /* Invalid mode. */ + HGCMSvcSetU32(&parms[0], 99); + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS); + + /* Enable transfers. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_ENABLED); + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + /* Disable transfers again. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_DISABLED); + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +static void testGuestSimple(void) +{ + RTTestISub("Testing client (guest) API - Simple"); + + PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); + + /* Preparations. */ + VBGLR3SHCLCMDCTX Ctx; + RT_ZERO(Ctx); + + /* + * Multiple connects / disconnects. + */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, VBOX_SHCL_GF_0_CONTEXT_ID)); + RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx)); + /* Report bogus guest features while connecting. */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, 0xdeadbeef)); + RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx)); + + RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, VBOX_SHCL_GF_0_CONTEXT_ID)); + + /* + * Feature tests. + */ + + RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFeatures(Ctx.idClient, 0x0, NULL /* pfHostFeatures */)); + /* Report bogus features to the host. */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFeatures(Ctx.idClient, 0xdeadb33f, NULL /* pfHostFeatures */)); + + /* + * Access denied tests. + */ + + /* Try reading data from host. */ + uint8_t abData[32]; uint32_t cbIgnored; + RTTESTI_CHECK_RC(VbglR3ClipboardReadData(Ctx.idClient, VBOX_SHCL_FMT_UNICODETEXT, + abData, sizeof(abData), &cbIgnored), VERR_ACCESS_DENIED); + /* Try writing data without reporting formats before (legacy). */ + RTTESTI_CHECK_RC(VbglR3ClipboardWriteData(Ctx.idClient, 0xdeadb33f, abData, sizeof(abData)), VERR_ACCESS_DENIED); + /* Try writing data without reporting formats before. */ + RTTESTI_CHECK_RC(VbglR3ClipboardWriteDataEx(&Ctx, 0xdeadb33f, abData, sizeof(abData)), VERR_ACCESS_DENIED); + /* Report bogus formats to the host. */ + RTTESTI_CHECK_RC(VbglR3ClipboardReportFormats(Ctx.idClient, 0xdeadb33f), VERR_ACCESS_DENIED); + /* Report supported formats to host. */ + RTTESTI_CHECK_RC(VbglR3ClipboardReportFormats(Ctx.idClient, + VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_BITMAP | VBOX_SHCL_FMT_HTML), + VERR_ACCESS_DENIED); + /* + * Access allowed tests. + */ + tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_BIDIRECTIONAL); + + /* Try writing data without reporting formats before. */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardWriteDataEx(&Ctx, 0xdeadb33f, abData, sizeof(abData))); + /* Try reading data from host. */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardReadData(Ctx.idClient, VBOX_SHCL_FMT_UNICODETEXT, + abData, sizeof(abData), &cbIgnored)); + /* Report bogus formats to the host. */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFormats(Ctx.idClient, 0xdeadb33f)); + /* Report supported formats to host. */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFormats(Ctx.idClient, + VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_BITMAP | VBOX_SHCL_FMT_HTML)); + /* Tear down. */ + RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx)); +} + +static RTUTF16 tstGetRandUtf8(void) +{ + return RTRandU32Ex(0x20, 0x7A); +} + +static char *tstGenerateUtf8StringA(uint32_t uCch) +{ + char * pszRand = (char *)RTMemAlloc(uCch + 1); + for (uint32_t i = 0; i < uCch; i++) + pszRand[i] = tstGetRandUtf8(); + pszRand[uCch] = 0; + return pszRand; +} + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) +static RTUTF16 tstGetRandUtf16(void) +{ + RTUTF16 wc; + do + { + wc = (RTUTF16)RTRandU32Ex(1, 0xfffd); + } while (wc >= 0xd800 && wc <= 0xdfff); + return wc; +} + +static PRTUTF16 tstGenerateUtf16StringA(uint32_t uCch) +{ + PRTUTF16 pwszRand = (PRTUTF16)RTMemAlloc((uCch + 1) * sizeof(RTUTF16)); + for (uint32_t i = 0; i < uCch; i++) + pwszRand[i] = tstGetRandUtf16(); + pwszRand[uCch] = 0; + return pwszRand; +} +#endif /* RT_OS_WINDOWS) || RT_OS_OS2 */ + +static void testSetHeadless(void) +{ + RTTestISub("Testing HOST_FN_SET_HEADLESS"); + + PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); + + VBOXHGCMSVCPARM parms[2]; + HGCMSvcSetU32(&parms[0], false); + int rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + bool fHeadless = ShClSvcGetHeadless(); + RTTESTI_CHECK_MSG(fHeadless == false, ("fHeadless=%RTbool\n", fHeadless)); + rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 0, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 2, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + HGCMSvcSetU64(&parms[0], 99); + rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + HGCMSvcSetU32(&parms[0], true); + rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + fHeadless = ShClSvcGetHeadless(); + RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); + HGCMSvcSetU32(&parms[0], 99); + rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + fHeadless = ShClSvcGetHeadless(); + RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); +} + +static void testHostCall(void) +{ + tstOperationModes(); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + testSetTransferMode(); +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + testSetHeadless(); +} + + +/********************************************************************************************************************************* + * Test: Guest reading from host * + ********************************************************************************************************************************/ +#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) +/* Called from SHCLX11 thread. */ +static DECLCALLBACK(int) tstTestReadFromHost_ReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser) +{ + RT_NOREF(pCtx, fFormats, pvUser); + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "tstTestReadFromHost_SvcReportFormatsCallback: fFormats=%#x\n", fFormats); + return VINF_SUCCESS; +} + +/* Called by the backend, e.g. for X11 in the SHCLX11 thread. */ +static DECLCALLBACK(int) tstTestReadFromHost_OnClipboardReadCallback(PSHCLCONTEXT pCtx, + SHCLFORMAT uFmt, void **ppv, size_t *pcb, void *pvUser) +{ + RT_NOREF(pCtx, uFmt, pvUser); + + PCLIPBOARDTESTTASK pTask = (PCLIPBOARDTESTTASK)TstHGCMUtilsTaskGetCurrent(&g_TstCtx.HGCM)->pvUser; + + void *pvData = NULL; + size_t cbData = pTask->cbData - pTask->cbProcessed; + if (cbData) + { + pvData = RTMemDup((uint8_t *)pTask->pvData + pTask->cbProcessed, cbData); + AssertPtr(pvData); + } + + RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Host reporting back %RU32 bytes of data\n", cbData); + + *ppv = pvData; + *pcb = cbData; + + return VINF_SUCCESS; +} +#endif /* (RT_OS_LINUX) || defined (RT_OS_SOLARIS) */ + +typedef struct TSTUSERMOCK +{ +#if defined(RT_OS_LINUX) + SHCLX11CTX X11Ctx; +#endif + PSHCLCONTEXT pCtx; +} TSTUSERMOCK; +typedef TSTUSERMOCK *PTSTUSERMOCK; + +static void tstTestReadFromHost_MockInit(PTSTUSERMOCK pUsrMock, const char *pszName) +{ +#if defined(RT_OS_LINUX) + SHCLCALLBACKS Callbacks; + RT_ZERO(Callbacks); + Callbacks.pfnReportFormats = tstTestReadFromHost_ReportFormatsCallback; + Callbacks.pfnOnClipboardRead = tstTestReadFromHost_OnClipboardReadCallback; + + pUsrMock->pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); + AssertPtrReturnVoid(pUsrMock->pCtx); + + ShClX11Init(&pUsrMock->X11Ctx, &Callbacks, pUsrMock->pCtx, false); + ShClX11ThreadStartEx(&pUsrMock->X11Ctx, pszName, false /* fGrab */); + /* Give the clipboard time to synchronise. */ + RTThreadSleep(500); +#else + RT_NOREF(pUsrMock, pszName); +#endif /* RT_OS_LINUX */ +} + +static void tstTestReadFromHost_MockDestroy(PTSTUSERMOCK pUsrMock) +{ +#if defined(RT_OS_LINUX) + ShClX11ThreadStop(&pUsrMock->X11Ctx); + ShClX11Destroy(&pUsrMock->X11Ctx); + RTMemFree(pUsrMock->pCtx); +#else + RT_NOREF(pUsrMock); +#endif +} + +static int tstTestReadFromHost_DoIt(PCLIPBOARDTESTCTX pCtx, PCLIPBOARDTESTTASK pTask) +{ + size_t cbDst = RT_MAX(_64K, pTask->cbData); + uint8_t *pabDst = (uint8_t *)RTMemAllocZ(cbDst); + AssertPtrReturn(pabDst, VERR_NO_MEMORY); + + AssertPtr(pTask->pvData); /* Racing condition with host thread? */ + Assert(pTask->cbChunk); /* Buggy test? */ + Assert(pTask->cbChunk <= pTask->cbData); /* Ditto. */ + + size_t cbToRead = pTask->cbData; + switch (pTask->enmFmtGst) + { + case VBOX_SHCL_FMT_UNICODETEXT: +#ifndef RT_OS_WINDOWS /** @todo Not sure about OS/2. */ + cbToRead *= sizeof(RTUTF16); +#endif + break; + + default: + break; + } + + PVBGLR3SHCLCMDCTX pCmdCtx = &pCtx->Guest.CmdCtx; + + /* Do random chunked reads. */ + uint32_t const cChunkedReads = RTRandU32Ex(1, 16); + RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "%RU32 chunked reads\n", cChunkedReads); + for (uint32_t i = 0; i < cChunkedReads; i++) + { + /* Note! VbglR3ClipboardReadData() currently does not support chunked reads! + * It in turn returns VINF_BUFFER_OVERFLOW when the supplied buffer was too small. */ + + uint32_t cbChunk = RTRandU32Ex(1, (uint32_t)(pTask->cbData / cChunkedReads)); + uint32_t cbRead = 0; + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Guest trying to read %RU32 bytes\n", cbChunk); + int vrc2 = VbglR3ClipboardReadData(pCmdCtx->idClient, pTask->enmFmtGst, pabDst, cbChunk, &cbRead); + if ( vrc2 == VINF_SUCCESS + && cbRead == 0) /* No data there yet? */ + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "No data (yet) from host\n"); + RTThreadSleep(10); + continue; + } + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Trying reading host clipboard data with a %RU32 buffer -> %Rrc (%RU32)\n", cbChunk, vrc2, cbRead); + RTTEST_CHECK_MSG(g_hTest, vrc2 == VINF_BUFFER_OVERFLOW, (g_hTest, "Got %Rrc, expected VINF_BUFFER_OVERFLOW\n", vrc2)); + } + + /* Last read: Read the data with a buffer big enough. This must succeed. */ + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Reading full data (%zu)\n", pTask->cbData); + uint32_t cbRead = 0; + int vrc2 = VbglR3ClipboardReadData(pCmdCtx->idClient, pTask->enmFmtGst, pabDst, (uint32_t)cbDst, &cbRead); + RTTEST_CHECK_MSG(g_hTest, vrc2 == VINF_SUCCESS, (g_hTest, "Got %Rrc, expected VINF_SUCCESS\n", vrc2)); + RTTEST_CHECK_MSG(g_hTest, cbRead == cbToRead, (g_hTest, "Read %RU32 bytes, expected %zu\n", cbRead, cbToRead)); + + if (pTask->enmFmtGst == VBOX_SHCL_FMT_UNICODETEXT) + RTTEST_CHECK_MSG(g_hTest, RTUtf16ValidateEncoding((PRTUTF16)pabDst) == VINF_SUCCESS, (g_hTest, "Read data is not valid UTF-16\n")); + if (cbRead == cbToRead) + { +#ifndef RT_OS_WINDOWS /** @todo Not sure about OS/2. */ + PRTUTF16 pwszSrc = NULL; + RTTEST_CHECK(g_hTest, RT_SUCCESS(RTStrToUtf16((const char *)pTask->pvData, &pwszSrc))); + RTTEST_CHECK_MSG(g_hTest, memcmp(pwszSrc, pabDst, cbRead) == 0, (g_hTest, "Read data does not match host data\n")); + RTUtf16Free(pwszSrc); +#else + RTTEST_CHECK_MSG(g_hTest, memcmp(pTask->pvData, pabDst, cbRead) == 0, (g_hTest, "Read data does not match host data\n")); +#endif + } + + RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Read data from host:\n%.*Rhxd\n", cbRead, pabDst); + + RTMemFree(pabDst); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) tstTestReadFromHost_ThreadGuest(PTSTHGCMUTILSCTX pCtx, void *pvCtx) +{ + RTThreadSleep(1000); /* Fudge; wait until the host has prepared the data for the clipboard. */ + + PCLIPBOARDTESTCTX pTstCtx = (PCLIPBOARDTESTCTX)pvCtx; + AssertPtr(pTstCtx); + + RT_ZERO(pTstCtx->Guest.CmdCtx); + RTTEST_CHECK_RC_OK(g_hTest, VbglR3ClipboardConnectEx(&pTstCtx->Guest.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID)); + + RTThreadSleep(1000); /* Fudge; wait until the host has prepared the data for the clipboard. */ + + PCLIPBOARDTESTTASK pTstTask = (PCLIPBOARDTESTTASK)pCtx->Task.pvUser; + AssertPtr(pTstTask); + tstTestReadFromHost_DoIt(pTstCtx, pTstTask); + + /* Signal that the task ended. */ + TstHGCMUtilsTaskSignal(&pCtx->Task, VINF_SUCCESS); + + RTTEST_CHECK_RC_OK(g_hTest, VbglR3ClipboardDisconnectEx(&pTstCtx->Guest.CmdCtx)); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) tstTestReadFromHost_ClientConnectedCallback(PTSTHGCMUTILSCTX pCtx, PTSTHGCMMOCKCLIENT pClient, + void *pvUser) +{ + RT_NOREF(pCtx, pClient); + + PCLIPBOARDTESTCTX pTstCtx = (PCLIPBOARDTESTCTX)pvUser; + AssertPtr(pTstCtx); RT_NOREF(pTstCtx); + + RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Client %RU32 connected\n", pClient->idClient); + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) tstTestReadFromHostSetup(PCLIPBOARDTESTCTX pTstCtx, void **ppvCtx) +{ + RT_NOREF(ppvCtx); + + /* Set the right clipboard mode, so that the guest can read from the host. */ + tstClipboardSetMode(TstHgcmMockSvcInst(), VBOX_SHCL_MODE_BIDIRECTIONAL); + + /* Start the host thread first, so that the guest thread can connect to it later. */ + TSTHGCMUTILSHOSTCALLBACKS HostCallbacks; + RT_ZERO(HostCallbacks); + HostCallbacks.pfnOnClientConnected = tstTestReadFromHost_ClientConnectedCallback; + TstHGCMUtilsHostThreadStart(&pTstCtx->HGCM, &HostCallbacks, pTstCtx /* pvUser */); + + PCLIPBOARDTESTTASK pTask = &pTstCtx->Task; + AssertPtr(pTask); + pTask->enmFmtGst = VBOX_SHCL_FMT_UNICODETEXT; + pTask->enmFmtHst = pTask->enmFmtGst; + pTask->cbChunk = RTRandU32Ex(1, 512); + pTask->cbData = RT_ALIGN_32(pTask->cbChunk * RTRandU32Ex(1, 16), 2); + Assert(pTask->cbData % sizeof(RTUTF16) == 0); +#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2) + pTask->pvData = tstGenerateUtf8StringA(pTask->cbData); + pTask->cbData++; /* Add terminating zero. */ +#else + pTask->pvData = tstGenerateUtf16StringA((uint32_t)(pTask->cbData /* We use bytes == chars here */)); + pTask->cbData *= sizeof(RTUTF16); + pTask->cbData += sizeof(RTUTF16); /* Add terminating zero. */ +#endif + pTask->cbProcessed = 0; + + int rc = VINF_SUCCESS; + +#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) + /* Initialize the Shared Clipboard backend callbacks. */ + PSHCLBACKEND pBackend = ShClSvcGetBackend(); + + SHCLCALLBACKS ShClCallbacks; + RT_ZERO(ShClCallbacks); + ShClCallbacks.pfnReportFormats = tstTestReadFromHost_ReportFormatsCallback; + ShClCallbacks.pfnOnClipboardRead = tstTestReadFromHost_OnClipboardReadCallback; + ShClBackendSetCallbacks(pBackend, &ShClCallbacks); +#elif defined (RT_OS_WINDOWS) + rc = SharedClipboardWinOpen(GetDesktopWindow()); + if (RT_SUCCESS(rc)) + { + rc = SharedClipboardWinDataWrite(CF_UNICODETEXT, pTask->pvData, (uint32_t)pTask->cbData); + SharedClipboardWinClose(); + } +#endif /* defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) */ + + RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Host data (%RU32):\n%.*Rhxd\n", pTask->cbData, pTask->cbData, pTask->pvData); + return rc; +} + +static DECLCALLBACK(int) tstTestReadFromHostExec(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx) +{ + RT_NOREF(pvCtx); + + TstHGCMUtilsGuestThreadStart(&pTstCtx->HGCM, tstTestReadFromHost_ThreadGuest, pTstCtx); + + PTSTHGCMUTILSTASK pTask = (PTSTHGCMUTILSTASK)TstHGCMUtilsTaskGetCurrent(&pTstCtx->HGCM); + + bool fUseMock = false; + TSTUSERMOCK UsrMock; + if (fUseMock) + tstTestReadFromHost_MockInit(&UsrMock, "tstX11Hst"); + + /* Wait until the task has been finished. */ + TstHGCMUtilsTaskWait(pTask, RT_MS_30SEC); + + if (fUseMock) + tstTestReadFromHost_MockDestroy(&UsrMock); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) tstTestReadFromHostDestroy(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx) +{ + RT_NOREF(pvCtx); + + int vrc = TstHGCMUtilsGuestThreadStop(&pTstCtx->HGCM); + AssertRC(vrc); + vrc = TstHGCMUtilsHostThreadStop(&pTstCtx->HGCM); + AssertRC(vrc); + + return vrc; +} + + +/********************************************************************************************************************************* + * Main * + ********************************************************************************************************************************/ + +/** Test definition table. */ +CLIPBOARDTESTDESC g_aTests[] = +{ + /* Tests guest reading clipboard data from the host. */ + { tstTestReadFromHostSetup, tstTestReadFromHostExec, tstTestReadFromHostDestroy } +}; +/** Number of tests defined. */ +unsigned g_cTests = RT_ELEMENTS(g_aTests); + +static int tstOne(PTESTDESC pTstDesc) +{ + PCLIPBOARDTESTCTX pTstCtx = &g_TstCtx; + + void *pvCtx; + int rc = pTstDesc->pfnSetup(pTstCtx, &pvCtx); + if (RT_SUCCESS(rc)) + { + rc = pTstDesc->pfnExec(pTstCtx, pvCtx); + + int rc2 = pTstDesc->pfnDestroy(pTstCtx, pvCtx); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + +int main(int argc, char *argv[]) +{ + /* + * Init the runtime, test and say hello. + */ + const char *pcszExecName; + NOREF(argc); + pcszExecName = strrchr(argv[0], '/'); + pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0]; + RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(g_hTest); + +#ifndef DEBUG_andy + /* Don't let assertions in the host service panic (core dump) the test cases. */ + RTAssertSetMayPanic(false); +#endif + + PTSTHGCMMOCKSVC const pSvc = TstHgcmMockSvcInst(); + TstHgcmMockSvcCreate(pSvc, sizeof(SHCLCLIENT)); + TstHgcmMockSvcStart(pSvc); + + /* + * Run the tests. + */ + if (0) + { + testGuestSimple(); + testHostCall(); + } + + RT_ZERO(g_TstCtx); + + PTSTHGCMUTILSCTX pCtx = &g_TstCtx.HGCM; + TstHGCMUtilsCtxInit(pCtx, pSvc); + + PTSTHGCMUTILSTASK pTask = (PTSTHGCMUTILSTASK)TstHGCMUtilsTaskGetCurrent(pCtx); + TstHGCMUtilsTaskInit(pTask); + pTask->pvUser = &g_TstCtx.Task; + + for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++) + tstOne(&g_aTests[i]); + + TstHGCMUtilsTaskDestroy(pTask); + + TstHgcmMockSvcStop(pSvc); + TstHgcmMockSvcDestroy(pSvc); + + /* + * Summary + */ + return RTTestSummaryAndDestroy(g_hTest); +} + diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp new file mode 100644 index 00000000..28130531 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp @@ -0,0 +1,336 @@ +/* $Id: tstClipboardServiceHost.cpp $ */ +/** @file + * Shared Clipboard host service test case. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "../VBoxSharedClipboardSvc-internal.h" + +#include <VBox/HostServices/VBoxClipboardSvc.h> + +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/test.h> + +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *ptable); + +static SHCLCLIENT g_Client; +static VBOXHGCMSVCHELPERS g_Helpers = { NULL }; + +/** Simple call handle structure for the guest call completion callback */ +struct VBOXHGCMCALLHANDLE_TYPEDEF +{ + /** Where to store the result code */ + int32_t rc; +}; + +/** Call completion callback for guest calls. */ +static DECLCALLBACK(int) callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc) +{ + callHandle->rc = rc; + return VINF_SUCCESS; +} + +static int setupTable(VBOXHGCMSVCFNTABLE *pTable) +{ + pTable->cbSize = sizeof(*pTable); + pTable->u32Version = VBOX_HGCM_SVC_VERSION; + g_Helpers.pfnCallComplete = callComplete; + pTable->pHelpers = &g_Helpers; + return VBoxHGCMSvcLoad(pTable); +} + +static void testSetMode(void) +{ + struct VBOXHGCMSVCPARM parms[2]; + VBOXHGCMSVCFNTABLE table; + uint32_t u32Mode; + int rc; + + RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_MODE"); + rc = setupTable(&table); + RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); + + /* Reset global variable which doesn't reset itself. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_OFF); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + u32Mode = ShClSvcGetMode(); + RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode)); + + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 0, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 2, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + HGCMSvcSetU64(&parms[0], 99); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_HOST_TO_GUEST); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + u32Mode = ShClSvcGetMode(); + RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_HOST_TO_GUEST, ("u32Mode=%u\n", (unsigned) u32Mode)); + + HGCMSvcSetU32(&parms[0], 99); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_NOT_SUPPORTED); + + u32Mode = ShClSvcGetMode(); + RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode)); + table.pfnUnload(NULL); +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +static void testSetTransferMode(void) +{ + struct VBOXHGCMSVCPARM parms[2]; + VBOXHGCMSVCFNTABLE table; + + RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE"); + int rc = setupTable(&table); + RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); + + /* Invalid parameter. */ + HGCMSvcSetU64(&parms[0], 99); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + + /* Invalid mode. */ + HGCMSvcSetU32(&parms[0], 99); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS); + + /* Enable transfers. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_ENABLED); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + /* Disable transfers again. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_DISABLED); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +/* Adds a host data read request message to the client's message queue. */ +static void testMsgAddReadData(PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + int rc = ShClSvcGuestDataRequest(pClient, fFormats, NULL /* pidEvent */); + RTTESTI_CHECK_RC_OK(rc); +} + +/* Does testing of VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, needed for providing compatibility to older Guest Additions clients. */ +static void testGetHostMsgOld(void) +{ + struct VBOXHGCMSVCPARM parms[2]; + VBOXHGCMSVCFNTABLE table; + VBOXHGCMCALLHANDLE_TYPEDEF call; + int rc; + + RTTestISub("Setting up VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT test"); + rc = setupTable(&table); + RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); + /* Unless we are bidirectional the host message requests will be dropped. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + + + RTTestISub("Testing one format, waiting guest call."); + RT_ZERO(g_Client); + HGCMSvcSetU32(&parms[0], 0); + HGCMSvcSetU32(&parms[1], 0); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This should get updated only when the guest call completes. */ + testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_UNICODETEXT); + RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); + RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); + RTTESTI_CHECK_RC_OK(call.rc); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ + table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); + + RTTestISub("Testing one format, no waiting guest calls."); + RT_ZERO(g_Client); + table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); + testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_HTML); + HGCMSvcSetU32(&parms[0], 0); + HGCMSvcSetU32(&parms[1], 0); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); + RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_HTML); + RTTESTI_CHECK_RC_OK(call.rc); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ + table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); + + RTTestISub("Testing two formats, waiting guest call."); + RT_ZERO(g_Client); + table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); + HGCMSvcSetU32(&parms[0], 0); + HGCMSvcSetU32(&parms[1], 0); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This should get updated only when the guest call completes. */ + testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML); + RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); + RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); + RTTESTI_CHECK_RC_OK(call.rc); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); + RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_HTML); + RTTESTI_CHECK_RC_OK(call.rc); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ + table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); + + RTTestISub("Testing two formats, no waiting guest calls."); + RT_ZERO(g_Client); + table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); + testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML); + HGCMSvcSetU32(&parms[0], 0); + HGCMSvcSetU32(&parms[1], 0); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); + RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); + RTTESTI_CHECK_RC_OK(call.rc); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); + RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_HTML); + RTTESTI_CHECK_RC_OK(call.rc); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); + RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ + table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); + table.pfnUnload(NULL); +} + +static void testSetHeadless(void) +{ + struct VBOXHGCMSVCPARM parms[2]; + VBOXHGCMSVCFNTABLE table; + bool fHeadless; + int rc; + + RTTestISub("Testing HOST_FN_SET_HEADLESS"); + rc = setupTable(&table); + RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); + /* Reset global variable which doesn't reset itself. */ + HGCMSvcSetU32(&parms[0], false); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, + 1, parms); + RTTESTI_CHECK_RC_OK(rc); + fHeadless = ShClSvcGetHeadless(); + RTTESTI_CHECK_MSG(fHeadless == false, ("fHeadless=%RTbool\n", fHeadless)); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, + 0, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, + 2, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + HGCMSvcSetU64(&parms[0], 99); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, + 1, parms); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + HGCMSvcSetU32(&parms[0], true); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, + 1, parms); + RTTESTI_CHECK_RC_OK(rc); + fHeadless = ShClSvcGetHeadless(); + RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); + HGCMSvcSetU32(&parms[0], 99); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, + 1, parms); + RTTESTI_CHECK_RC_OK(rc); + fHeadless = ShClSvcGetHeadless(); + RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); + table.pfnUnload(NULL); +} + +static void testHostCall(void) +{ + testSetMode(); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + testSetTransferMode(); +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + testSetHeadless(); +} + +int main(int argc, char *argv[]) +{ + /* + * Init the runtime, test and say hello. + */ + const char *pcszExecName; + NOREF(argc); + pcszExecName = strrchr(argv[0], '/'); + pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0]; + RTTEST hTest; + RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(hTest); + + /* Don't let assertions in the host service panic (core dump) the test cases. */ + RTAssertSetMayPanic(false); + + /* + * Run the tests. + */ + testHostCall(); + testGetHostMsgOld(); + + /* + * Summary + */ + return RTTestSummaryAndDestroy(hTest); +} + +int ShClBackendInit(PSHCLBACKEND, VBOXHGCMSVCFNTABLE *) { return VINF_SUCCESS; } +void ShClBackendDestroy(PSHCLBACKEND) { } +int ShClBackendDisconnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } +int ShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT, bool) { return VINF_SUCCESS; } +int ShClBackendReportFormats(PSHCLBACKEND, PSHCLCLIENT, SHCLFORMATS) { AssertFailed(); return VINF_SUCCESS; } +int ShClBackendReadData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t, unsigned int *) { AssertFailed(); return VERR_WRONG_ORDER; } +int ShClBackendWriteData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t) { AssertFailed(); return VINF_SUCCESS; } +int ShClBackendSync(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +int ShClBackendTransferCreate(PSHCLBACKEND, PSHCLCLIENT, PSHCLTRANSFER) { return VINF_SUCCESS; } +int ShClBackendTransferDestroy(PSHCLBACKEND, PSHCLCLIENT, PSHCLTRANSFER) { return VINF_SUCCESS; } +int ShClBackendTransferGetRoots(PSHCLBACKEND, PSHCLCLIENT, PSHCLTRANSFER) { return VINF_SUCCESS; } +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp new file mode 100644 index 00000000..cfb4b665 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp @@ -0,0 +1,205 @@ +/* $Id: tstClipboardServiceImpl.cpp $ */ +/** @file + * Shared Clipboard host service implementation (backend) test case. + */ + +/* + * Copyright (C) 2020-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "../VBoxSharedClipboardSvc-internal.h" + +#include <VBox/HostServices/VBoxClipboardSvc.h> +#ifdef RT_OS_WINDOWS +# include <VBox/GuestHost/SharedClipboard-win.h> +#endif + +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/test.h> + +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *ptable); + +static SHCLCLIENT g_Client; +static VBOXHGCMSVCHELPERS g_Helpers = { NULL }; + +/** Simple call handle structure for the guest call completion callback */ +struct VBOXHGCMCALLHANDLE_TYPEDEF +{ + /** Where to store the result code */ + int32_t rc; +}; + +/** Call completion callback for guest calls. */ +static DECLCALLBACK(int) callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc) +{ + callHandle->rc = rc; + return VINF_SUCCESS; +} + +static int setupTable(VBOXHGCMSVCFNTABLE *pTable) +{ + pTable->cbSize = sizeof(*pTable); + pTable->u32Version = VBOX_HGCM_SVC_VERSION; + g_Helpers.pfnCallComplete = callComplete; + pTable->pHelpers = &g_Helpers; + return VBoxHGCMSvcLoad(pTable); +} + +int ShClBackendInit(PSHCLBACKEND, VBOXHGCMSVCFNTABLE *) { return VINF_SUCCESS; } +void ShClBackendDestroy(PSHCLBACKEND) { } +int ShClBackendDisconnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } +int ShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT, bool) { return VINF_SUCCESS; } +int ShClBackendReportFormats(PSHCLBACKEND, PSHCLCLIENT, SHCLFORMATS) { AssertFailed(); return VINF_SUCCESS; } +int ShClBackendReadData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t, unsigned int *) { AssertFailed(); return VERR_WRONG_ORDER; } +int ShClBackendWriteData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t) { AssertFailed(); return VINF_SUCCESS; } +int ShClBackendSync(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } + +static void testAnnounceAndReadData(void) +{ + struct VBOXHGCMSVCPARM parms[2]; + VBOXHGCMSVCFNTABLE table; + int rc; + + RTTestISub("Setting up client ..."); + RTTestIDisableAssertions(); + + rc = setupTable(&table); + RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); + /* Unless we are bidirectional the host message requests will be dropped. */ + HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); + RTTESTI_CHECK_RC_OK(rc); + rc = shClSvcClientInit(&g_Client, 1 /* clientId */); + RTTESTI_CHECK_RC_OK(rc); + + RTTestIRestoreAssertions(); +} + +#ifdef RT_OS_WINDOWS +# include "VBoxOrgCfHtml1.h" /* From chrome 97.0.4692.71 */ +# include "VBoxOrgMimeHtml1.h" + +static void testHtmlCf(void) +{ + RTTestISub("CF_HTML"); + + char *pszOutput = NULL; + uint32_t cbOutput = UINT32_MAX/2; + RTTestIDisableAssertions(); + RTTESTI_CHECK_RC(SharedClipboardWinConvertCFHTMLToMIME("", 0, &pszOutput, &cbOutput), VERR_INVALID_PARAMETER); + RTTestIRestoreAssertions(); + + pszOutput = NULL; + cbOutput = UINT32_MAX/2; + RTTESTI_CHECK_RC(SharedClipboardWinConvertCFHTMLToMIME((char *)&g_abVBoxOrgCfHtml1[0], g_cbVBoxOrgCfHtml1, + &pszOutput, &cbOutput), VINF_SUCCESS); + RTTESTI_CHECK(cbOutput == g_cbVBoxOrgMimeHtml1); + RTTESTI_CHECK(memcmp(pszOutput, g_abVBoxOrgMimeHtml1, cbOutput) == 0); + RTMemFree(pszOutput); + + + static RTSTRTUPLE const s_aRoundTrips[] = + { + { RT_STR_TUPLE("") }, + { RT_STR_TUPLE("1") }, + { RT_STR_TUPLE("12") }, + { RT_STR_TUPLE("123") }, + { RT_STR_TUPLE("1234") }, + { RT_STR_TUPLE("12345") }, + { RT_STR_TUPLE("123456") }, + { RT_STR_TUPLE("1234567") }, + { RT_STR_TUPLE("12345678") }, + { RT_STR_TUPLE("123456789") }, + { RT_STR_TUPLE("1234567890") }, + { RT_STR_TUPLE("<h2>asdfkjhasdflhj</h2>") }, + { RT_STR_TUPLE("<h2>asdfkjhasdflhj</h2>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0") }, + { (const char *)g_abVBoxOrgMimeHtml1, sizeof(g_abVBoxOrgMimeHtml1) }, + }; + + for (size_t i = 0; i < RT_ELEMENTS(s_aRoundTrips); i++) + { + int rc; + char *pszCfHtml = NULL; + uint32_t cbCfHtml = UINT32_MAX/2; + rc = SharedClipboardWinConvertMIMEToCFHTML(s_aRoundTrips[i].psz, s_aRoundTrips[i].cch + 1, &pszCfHtml, &cbCfHtml); + if (rc == VINF_SUCCESS) + { + if (strlen(pszCfHtml) + 1 != cbCfHtml) + RTTestIFailed("#%u: SharedClipboardWinConvertMIMEToCFHTML(%s, %#zx,,) returned incorrect length: %#x, actual %#zx", + i, s_aRoundTrips[i].psz, s_aRoundTrips[i].cch, cbCfHtml, strlen(pszCfHtml) + 1); + + char *pszHtml = NULL; + uint32_t cbHtml = UINT32_MAX/4; + rc = SharedClipboardWinConvertCFHTMLToMIME(pszCfHtml, (uint32_t)strlen(pszCfHtml), &pszHtml, &cbHtml); + if (rc == VINF_SUCCESS) + { + if (strlen(pszHtml) + 1 != cbHtml) + RTTestIFailed("#%u: SharedClipboardWinConvertCFHTMLToMIME(%s, %#zx,,) returned incorrect length: %#x, actual %#zx", + i, pszHtml, strlen(pszHtml), cbHtml, strlen(pszHtml) + 1); + if (strcmp(pszHtml, s_aRoundTrips[i].psz) != 0) + RTTestIFailed("#%u: roundtrip for '%s' LB %#zx failed, ended up with '%s'", + i, s_aRoundTrips[i].psz, s_aRoundTrips[i].cch, pszHtml); + RTMemFree(pszHtml); + } + else + RTTestIFailed("#%u: SharedClipboardWinConvertCFHTMLToMIME(%s, %#zx,,) returned %Rrc, expected VINF_SUCCESS", + i, pszCfHtml, strlen(pszCfHtml), rc); + RTMemFree(pszCfHtml); + } + else + RTTestIFailed("#%u: SharedClipboardWinConvertMIMEToCFHTML(%s, %#zx,,) returned %Rrc, expected VINF_SUCCESS", + i, s_aRoundTrips[i].psz, s_aRoundTrips[i].cch, rc); + } +} + +#endif /* RT_OS_WINDOWS */ + + +int main(int argc, char *argv[]) +{ + /* + * Init the runtime, test and say hello. + */ + const char *pcszExecName; + NOREF(argc); + pcszExecName = strrchr(argv[0], '/'); + pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0]; + RTTEST hTest; + RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(hTest); + + /* + * Run the tests. + */ + testAnnounceAndReadData(); +#ifdef RT_OS_WINDOWS + testHtmlCf(); +#endif + + /* + * Summary + */ + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp new file mode 100644 index 00000000..dfe363ee --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp @@ -0,0 +1,389 @@ +/* $Id: tstClipboardTransfers.cpp $ */ +/** @file + * Shared Clipboard transfers test case. + */ + +/* + * Copyright (C) 2019-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "../VBoxSharedClipboardSvc-internal.h" + +#include <VBox/HostServices/VBoxClipboardSvc.h> + +#include <iprt/assert.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/test.h> + + +static int testCreateTempDir(RTTEST hTest, const char *pszTestcase, char *pszTempDir, size_t cbTempDir) +{ + char szTempDir[RTPATH_MAX]; + int rc = RTPathTemp(szTempDir, sizeof(szTempDir)); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + rc = RTPathAppend(szTempDir, sizeof(szTempDir), "tstClipboardTransfers"); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + rc = RTDirCreate(szTempDir, 0700, 0); + if (rc == VERR_ALREADY_EXISTS) + rc = VINF_SUCCESS; + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + rc = RTPathAppend(szTempDir, sizeof(szTempDir), "XXXXX"); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + rc = RTDirCreateTemp(szTempDir, 0700); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + rc = RTPathJoin(pszTempDir, cbTempDir, szTempDir, pszTestcase); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Created temporary directory: %s\n", pszTempDir); + + return rc; +} + +static int testRemoveTempDir(RTTEST hTest) +{ + char szTempDir[RTPATH_MAX]; + int rc = RTPathTemp(szTempDir, sizeof(szTempDir)); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + rc = RTPathAppend(szTempDir, sizeof(szTempDir), "tstClipboardTransfers"); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + rc = RTDirRemoveRecursive(szTempDir, RTDIRRMREC_F_CONTENT_AND_DIR); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Removed temporary directory: %s\n", szTempDir); + + return rc; +} + +static int testCreateDir(RTTEST hTest, const char *pszPathToCreate) +{ + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Creating directory: %s\n", pszPathToCreate); + + int rc = RTDirCreateFullPath(pszPathToCreate, 0700); + if (rc == VERR_ALREADY_EXISTS) + rc = VINF_SUCCESS; + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + return rc; +} + +static int testCreateFile(RTTEST hTest, const char *pszTempDir, const char *pszFileName, uint32_t fOpen, size_t cbSize, + char **ppszFilePathAbs) +{ + char szFilePath[RTPATH_MAX]; + + int rc = RTStrCopy(szFilePath, sizeof(szFilePath), pszTempDir); + RTTESTI_CHECK_RC_OK_RET(rc, rc); + + rc = RTPathAppend(szFilePath, sizeof(szFilePath), pszFileName); + RTTESTI_CHECK_RC_OK_RET(rc, rc); + + char *pszDirToCreate = RTStrDup(szFilePath); + RTTESTI_CHECK_RET(pszDirToCreate, VERR_NO_MEMORY); + + RTPathStripFilename(pszDirToCreate); + + rc = testCreateDir(hTest, pszDirToCreate); + RTTESTI_CHECK_RC_OK_RET(rc, rc); + + RTStrFree(pszDirToCreate); + pszDirToCreate = NULL; + + if (!fOpen) + fOpen = RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE; + + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Creating file: %s\n", szFilePath); + + RTFILE hFile; + rc = RTFileOpen(&hFile, szFilePath, fOpen); + if (RT_SUCCESS(rc)) + { + if (cbSize) + { + /** @todo Fill in some random stuff. */ + } + + rc = RTFileClose(hFile); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + } + + if (ppszFilePathAbs) + *ppszFilePathAbs = RTStrDup(szFilePath); + + return rc; +} + +typedef struct TESTTRANSFERROOTENTRY +{ + TESTTRANSFERROOTENTRY(const RTCString &a_strPath) + : strPath(a_strPath) { } + + RTCString strPath; +} TESTTRANSFERROOTENTRY; + +static int testAddRootEntry(RTTEST hTest, const char *pszTempDir, + const TESTTRANSFERROOTENTRY &rootEntry, char **ppszRoots) +{ + char *pszRoots = NULL; + + const char *pszPath = rootEntry.strPath.c_str(); + + char *pszPathAbs; + int rc = testCreateFile(hTest, pszTempDir, pszPath, 0, 0, &pszPathAbs); + RTTESTI_CHECK_RC_OK_RET(rc, rc); + + rc = RTStrAAppend(&pszRoots, pszPathAbs); + RTTESTI_CHECK_RC_OK(rc); + + rc = RTStrAAppend(&pszRoots, "\r\n"); + RTTESTI_CHECK_RC_OK(rc); + + RTStrFree(pszPathAbs); + + *ppszRoots = pszRoots; + + return rc; +} + +static int testAddRootEntries(RTTEST hTest, const char *pszTempDir, + RTCList<TESTTRANSFERROOTENTRY> &lstBase, RTCList<TESTTRANSFERROOTENTRY> lstToExtend, + char **ppszRoots) +{ + int rc = VINF_SUCCESS; + + char *pszRoots = NULL; + + for (size_t i = 0; i < lstBase.size(); ++i) + { + char *pszEntry = NULL; + rc = testAddRootEntry(hTest, pszTempDir, lstBase.at(i), &pszEntry); + RTTESTI_CHECK_RC_OK_BREAK(rc); + rc = RTStrAAppend(&pszRoots, pszEntry); + RTTESTI_CHECK_RC_OK_BREAK(rc); + RTStrFree(pszEntry); + } + + for (size_t i = 0; i < lstToExtend.size(); ++i) + { + char *pszEntry = NULL; + rc = testAddRootEntry(hTest, pszTempDir, lstToExtend.at(i), &pszEntry); + RTTESTI_CHECK_RC_OK_BREAK(rc); + rc = RTStrAAppend(&pszRoots, pszEntry); + RTTESTI_CHECK_RC_OK_BREAK(rc); + RTStrFree(pszEntry); + } + + if (RT_SUCCESS(rc)) + *ppszRoots = pszRoots; + + return rc; +} + +static void testTransferRootsSetSingle(RTTEST hTest, + RTCList<TESTTRANSFERROOTENTRY> &lstBase, RTCList<TESTTRANSFERROOTENTRY> lstToExtend, + int rcExpected) +{ + PSHCLTRANSFER pTransfer; + int rc = ShClTransferCreate(&pTransfer); + RTTESTI_CHECK_RC_OK(rc); + + char szTestTransferRootsSetDir[RTPATH_MAX]; + rc = testCreateTempDir(hTest, "testTransferRootsSet", szTestTransferRootsSetDir, sizeof(szTestTransferRootsSetDir)); + RTTESTI_CHECK_RC_OK_RETV(rc); + + /* This is the file we're trying to access (but not supposed to). */ + rc = testCreateFile(hTest, szTestTransferRootsSetDir, "must-not-access-this", 0, 0, NULL); + RTTESTI_CHECK_RC_OK(rc); + + char *pszRoots; + rc = testAddRootEntries(hTest, szTestTransferRootsSetDir, lstBase, lstToExtend, &pszRoots); + RTTESTI_CHECK_RC_OK_RETV(rc); + + rc = ShClTransferRootsSet(pTransfer, pszRoots, strlen(pszRoots) + 1); + RTTESTI_CHECK_RC(rc, rcExpected); + + RTStrFree(pszRoots); + + rc = ShClTransferDestroy(pTransfer); + RTTESTI_CHECK_RC_OK(rc); +} + +static void testTransferObjOpenSingle(RTTEST hTest, + RTCList<TESTTRANSFERROOTENTRY> &lstRoots, const char *pszObjPath, int rcExpected) +{ + RT_NOREF(hTest); + + PSHCLTRANSFER pTransfer; + int rc = ShClTransferCreate(&pTransfer); + RTTESTI_CHECK_RC_OK(rc); + + rc = ShClTransferInit(pTransfer, SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_LOCAL); + RTTESTI_CHECK_RC_OK(rc); + + char szTestTransferObjOpenDir[RTPATH_MAX]; + rc = testCreateTempDir(hTest, "testTransferObjOpen", szTestTransferObjOpenDir, sizeof(szTestTransferObjOpenDir)); + RTTESTI_CHECK_RC_OK_RETV(rc); + + /* This is the file we're trying to access (but not supposed to). */ + rc = testCreateFile(hTest, szTestTransferObjOpenDir, "file1.txt", 0, 0, NULL); + RTTESTI_CHECK_RC_OK(rc); + + RTCList<TESTTRANSFERROOTENTRY> lstToExtendEmpty; + + char *pszRoots; + rc = testAddRootEntries(hTest, szTestTransferObjOpenDir, lstRoots, lstToExtendEmpty, &pszRoots); + RTTESTI_CHECK_RC_OK_RETV(rc); + + rc = ShClTransferRootsSet(pTransfer, pszRoots, strlen(pszRoots) + 1); + RTTESTI_CHECK_RC_OK(rc); + + RTStrFree(pszRoots); + + SHCLOBJOPENCREATEPARMS openCreateParms; + rc = ShClTransferObjOpenParmsInit(&openCreateParms); + RTTESTI_CHECK_RC_OK(rc); + + rc = RTStrCopy(openCreateParms.pszPath, openCreateParms.cbPath, pszObjPath); + RTTESTI_CHECK_RC_OK(rc); + + SHCLOBJHANDLE hObj; + rc = ShClTransferObjOpen(pTransfer, &openCreateParms, &hObj); + RTTESTI_CHECK_RC(rc, rcExpected); + if (RT_SUCCESS(rc)) + { + rc = ShClTransferObjClose(pTransfer, hObj); + RTTESTI_CHECK_RC_OK(rc); + } + + rc = ShClTransferDestroy(pTransfer); + RTTESTI_CHECK_RC_OK(rc); +} + +static void testTransferBasics(RTTEST hTest) +{ + RT_NOREF(hTest); + + RTTestISub("Testing transfer basics"); + + SHCLEVENTSOURCE Source; + int rc = ShClEventSourceCreate(&Source, 0); + RTTESTI_CHECK_RC_OK(rc); + rc = ShClEventSourceDestroy(&Source); + RTTESTI_CHECK_RC_OK(rc); + PSHCLTRANSFER pTransfer; + rc = ShClTransferCreate(&pTransfer); + RTTESTI_CHECK_RC_OK(rc); + rc = ShClTransferDestroy(pTransfer); + RTTESTI_CHECK_RC_OK(rc); +} + +static void testTransferRootsSet(RTTEST hTest) +{ + RTTestISub("Testing setting transfer roots"); + + /* Define the (valid) transfer root set. */ + RTCList<TESTTRANSFERROOTENTRY> lstBase; + lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/file1.txt")); + lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/file1.txt")); + lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/sub1/file1.txt")); + lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/file1.txt")); + lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/sub1/file1.txt")); + + RTCList<TESTTRANSFERROOTENTRY> lstBreakout; + testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VINF_SUCCESS); + + lstBreakout.clear(); + lstBase.append(TESTTRANSFERROOTENTRY("../must-not-access-this")); + testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); + + lstBreakout.clear(); + lstBase.append(TESTTRANSFERROOTENTRY("does-not-exist/file1.txt")); + testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); + + lstBreakout.clear(); + lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/../must-not-access-this")); + testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); + + lstBreakout.clear(); + lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/./../must-not-access-this")); + testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); + + lstBreakout.clear(); + lstBase.append(TESTTRANSFERROOTENTRY("../does-not-exist")); + testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); +} + +static void testTransferObjOpen(RTTEST hTest) +{ + RTTestISub("Testing setting transfer object open"); + + /* Define the (valid) transfer root set. */ + RTCList<TESTTRANSFERROOTENTRY> lstRoots; + lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/file1.txt")); + lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/file1.txt")); + lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/sub1/file1.txt")); + lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/file1.txt")); + lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/sub1/file1.txt")); + + testTransferObjOpenSingle(hTest, lstRoots, "file1.txt", VINF_SUCCESS); + testTransferObjOpenSingle(hTest, lstRoots, "does-not-exist.txt", VERR_PATH_NOT_FOUND); + testTransferObjOpenSingle(hTest, lstRoots, "dir1/does-not-exist.txt", VERR_PATH_NOT_FOUND); + testTransferObjOpenSingle(hTest, lstRoots, "../must-not-access-this.txt", VERR_INVALID_PARAMETER); + testTransferObjOpenSingle(hTest, lstRoots, "dir1/../../must-not-access-this.txt", VERR_INVALID_PARAMETER); +} + +int main(int argc, char *argv[]) +{ + /* + * Init the runtime, test and say hello. + */ + const char *pcszExecName; + NOREF(argc); + pcszExecName = strrchr(argv[0], '/'); + pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0]; + RTTEST hTest; + RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(hTest); + + testTransferBasics(hTest); + testTransferRootsSet(hTest); + testTransferObjOpen(hTest); + + int rc = testRemoveTempDir(hTest); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + /* + * Summary + */ + return RTTestSummaryAndDestroy(hTest); +} + |