summaryrefslogtreecommitdiffstats
path: root/src/VBox/GuestHost
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:49:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:49:04 +0000
commit16f504a9dca3fe3b70568f67b7d41241ae485288 (patch)
treec60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/GuestHost
parentInitial commit. (diff)
downloadvirtualbox-upstream.tar.xz
virtualbox-upstream.zip
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/GuestHost')
-rw-r--r--src/VBox/GuestHost/DragAndDrop/DnDDroppedFiles.cpp450
-rw-r--r--src/VBox/GuestHost/DragAndDrop/DnDMIME.cpp63
-rw-r--r--src/VBox/GuestHost/DragAndDrop/DnDPath.cpp212
-rw-r--r--src/VBox/GuestHost/DragAndDrop/DnDTransferList.cpp1295
-rw-r--r--src/VBox/GuestHost/DragAndDrop/DnDTransferObject.cpp706
-rw-r--r--src/VBox/GuestHost/DragAndDrop/DnDUtils.cpp177
-rw-r--r--src/VBox/GuestHost/DragAndDrop/Makefile.kmk73
-rw-r--r--src/VBox/GuestHost/DragAndDrop/testcase/Makefile.kmk81
-rw-r--r--src/VBox/GuestHost/DragAndDrop/testcase/tstDnDPath.cpp100
-rw-r--r--src/VBox/GuestHost/DragAndDrop/testcase/tstDnDTransferList.cpp191
-rw-r--r--src/VBox/GuestHost/DragAndDrop/testcase/tstDnDTransferObject.cpp128
-rw-r--r--src/VBox/GuestHost/DragAndDrop/testcase/tstDragAndDropQt/Makefile.kmk53
-rw-r--r--src/VBox/GuestHost/DragAndDrop/testcase/tstDragAndDropQt/tstDragAndDropQt.cpp67
-rw-r--r--src/VBox/GuestHost/HGSMI/.scm-settings31
-rw-r--r--src/VBox/GuestHost/HGSMI/HGSMICommon.cpp450
-rw-r--r--src/VBox/GuestHost/HGSMI/HGSMIMemAlloc.cpp684
-rw-r--r--src/VBox/GuestHost/HGSMI/Makefile.kmk59
-rw-r--r--src/VBox/GuestHost/Makefile.kmk45
-rw-r--r--src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp978
-rw-r--r--src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp205
-rw-r--r--src/VBox/GuestHost/SharedClipboard/ClipboardMIME.cpp53
-rw-r--r--src/VBox/GuestHost/SharedClipboard/ClipboardPath.cpp92
-rw-r--r--src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp382
-rw-r--r--src/VBox/GuestHost/SharedClipboard/Makefile.kmk36
-rw-r--r--src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp1226
-rw-r--r--src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp862
-rw-r--r--src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp3132
-rw-r--r--src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp1322
-rw-r--r--src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp2428
-rw-r--r--src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk76
-rw-r--r--src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp941
-rw-r--r--src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp103
-rw-r--r--src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/Makefile.kmk52
-rw-r--r--src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/tstClipboardQt.cpp47
34 files changed, 16800 insertions, 0 deletions
diff --git a/src/VBox/GuestHost/DragAndDrop/DnDDroppedFiles.cpp b/src/VBox/GuestHost/DragAndDrop/DnDDroppedFiles.cpp
new file mode 100644
index 00000000..57fd03bb
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDDroppedFiles.cpp
@@ -0,0 +1,450 @@
+/* $Id: DnDDroppedFiles.cpp $ */
+/** @file
+ * DnD - Directory handling.
+ */
+
+/*
+ * Copyright (C) 2014-2022 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_GUEST_DND
+#include <VBox/GuestHost/DragAndDrop.h>
+
+#include <iprt/assert.h>
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+#include <VBox/log.h>
+
+
+/*********************************************************************************************************************************
+* Prototypes *
+*********************************************************************************************************************************/
+static int dndDroppedFilesCloseInternal(PDNDDROPPEDFILES pDF);
+
+
+/**
+ * Initializes a DnD Dropped Files struct, internal version.
+ *
+ * @returns VBox status code.
+ * @param pDF DnD Dropped Files to initialize.
+ */
+static int dndDroppedFilesInitInternal(PDNDDROPPEDFILES pDF)
+{
+ pDF->m_fOpen = 0;
+ pDF->m_hDir = NIL_RTDIR;
+ pDF->pszPathAbs = NULL;
+
+ RTListInit(&pDF->m_lstDirs);
+ RTListInit(&pDF->m_lstFiles);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Initializes a DnD Dropped Files struct, extended version.
+ *
+ * @returns VBox status code.
+ * @param pDF DnD Dropped Files to initialize.
+ * @param fFlags Dropped Files flags to use for initialization.
+ */
+int DnDDroppedFilesInitEx(PDNDDROPPEDFILES pDF,
+ const char *pszPath, DNDURIDROPPEDFILEFLAGS fFlags /* = DNDURIDROPPEDFILE_FLAGS_NONE */)
+{
+ int rc = dndDroppedFilesInitInternal(pDF);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ return DnDDroppedFilesOpenEx(pDF, pszPath, fFlags);
+}
+
+/**
+ * Initializes a DnD Dropped Files struct.
+ *
+ * @returns VBox status code.
+ * @param pDF DnD Dropped Files to initialize.
+ */
+int DnDDroppedFilesInit(PDNDDROPPEDFILES pDF)
+{
+ return dndDroppedFilesInitInternal(pDF);
+}
+
+/**
+ * Destroys a DnD Dropped Files struct.
+ *
+ * Note: This does *not* (physically) delete any added content.
+ * Make sure to call DnDDroppedFilesReset() then.
+ *
+ * @param pDF DnD Dropped Files to destroy.
+ */
+void DnDDroppedFilesDestroy(PDNDDROPPEDFILES pDF)
+{
+ /* Only make sure to not leak any handles and stuff, don't delete any
+ * directories / files here. */
+ dndDroppedFilesCloseInternal(pDF);
+
+ RTStrFree(pDF->pszPathAbs);
+ pDF->pszPathAbs = NULL;
+}
+
+/**
+ * Adds a file reference to a Dropped Files directory.
+ *
+ * @returns VBox status code.
+ * @param pDF DnD Dropped Files to add file to.
+ * @param pszFile Path of file entry to add.
+ */
+int DnDDroppedFilesAddFile(PDNDDROPPEDFILES pDF, const char *pszFile)
+{
+ AssertPtrReturn(pszFile, VERR_INVALID_POINTER);
+
+ PDNDDROPPEDFILESENTRY pEntry = (PDNDDROPPEDFILESENTRY)RTMemAlloc(sizeof(DNDDROPPEDFILESENTRY));
+ if (!pEntry)
+ return VERR_NO_MEMORY;
+
+ pEntry->pszPath = RTStrDup(pszFile);
+ if (pEntry->pszPath)
+ {
+ RTListAppend(&pDF->m_lstFiles, &pEntry->Node);
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pEntry);
+ return VERR_NO_MEMORY;
+}
+
+/**
+ * Adds a directory reference to a Dropped Files directory.
+ *
+ * Note: This does *not* (recursively) add sub entries.
+ *
+ * @returns VBox status code.
+ * @param pDF DnD Dropped Files to add directory to.
+ * @param pszDir Path of directory entry to add.
+ */
+int DnDDroppedFilesAddDir(PDNDDROPPEDFILES pDF, const char *pszDir)
+{
+ AssertPtrReturn(pszDir, VERR_INVALID_POINTER);
+
+ PDNDDROPPEDFILESENTRY pEntry = (PDNDDROPPEDFILESENTRY)RTMemAlloc(sizeof(DNDDROPPEDFILESENTRY));
+ if (!pEntry)
+ return VERR_NO_MEMORY;
+
+ pEntry->pszPath = RTStrDup(pszDir);
+ if (pEntry->pszPath)
+ {
+ RTListAppend(&pDF->m_lstDirs, &pEntry->Node);
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pEntry);
+ return VERR_NO_MEMORY;
+}
+
+/**
+ * Closes the dropped files directory handle, internal version.
+ *
+ * @returns VBox status code.
+ * @param pDF DnD Dropped Files to close.
+ */
+static int dndDroppedFilesCloseInternal(PDNDDROPPEDFILES pDF)
+{
+ int rc;
+ if (pDF->m_hDir != NULL)
+ {
+ rc = RTDirClose(pDF->m_hDir);
+ if (RT_SUCCESS(rc))
+ pDF->m_hDir = NULL;
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Closes the dropped files directory handle.
+ *
+ * @returns VBox status code.
+ * @param pDF DnD Dropped Files to close.
+ */
+int DnDDroppedFilesClose(PDNDDROPPEDFILES pDF)
+{
+ return dndDroppedFilesCloseInternal(pDF);
+}
+
+/**
+ * Returns the absolute path of the dropped files directory.
+ *
+ * @returns Pointer to absolute path of the dropped files directory.
+ * @param pDF DnD Dropped Files return absolute path of the dropped files directory for.
+ */
+const char *DnDDroppedFilesGetDirAbs(PDNDDROPPEDFILES pDF)
+{
+ return pDF->pszPathAbs;
+}
+
+/**
+ * Returns whether the dropped files directory has been opened or not.
+ *
+ * @returns \c true if open, \c false if not.
+ * @param pDF DnD Dropped Files to return open status for.
+ */
+bool DnDDroppedFilesIsOpen(PDNDDROPPEDFILES pDF)
+{
+ return (pDF->m_hDir != NULL);
+}
+
+/**
+ * Opens (creates) the dropped files directory.
+ *
+ * @returns VBox status code.
+ * @param pDF DnD Dropped Files to open.
+ * @param pszPath Absolute path where to create the dropped files directory.
+ * @param fFlags Dropped files flags to use for this directory.
+ */
+int DnDDroppedFilesOpenEx(PDNDDROPPEDFILES pDF,
+ const char *pszPath, DNDURIDROPPEDFILEFLAGS fFlags /* = DNDURIDROPPEDFILE_FLAGS_NONE */)
+{
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(fFlags == 0, VERR_INVALID_PARAMETER); /* Flags not supported yet. */
+
+ int rc;
+
+ do
+ {
+ char szDropDir[RTPATH_MAX];
+ RTStrPrintf(szDropDir, sizeof(szDropDir), "%s", pszPath);
+
+ /** @todo On Windows we also could use the registry to override
+ * this path, on Posix a dotfile and/or a guest property
+ * can be used. */
+
+ /* Append our base drop directory. */
+ rc = RTPathAppend(szDropDir, sizeof(szDropDir), "VirtualBox Dropped Files"); /** @todo Make this tag configurable? */
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Create it when necessary. */
+ if (!RTDirExists(szDropDir))
+ {
+ rc = RTDirCreateFullPath(szDropDir, RTFS_UNIX_IRWXU);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ /* The actually drop directory consist of the current time stamp and a
+ * unique number when necessary. */
+ char szTime[64];
+ RTTIMESPEC time;
+ if (!RTTimeSpecToString(RTTimeNow(&time), szTime, sizeof(szTime)))
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ rc = DnDPathSanitizeFileName(szTime, sizeof(szTime));
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = RTPathAppend(szDropDir, sizeof(szDropDir), szTime);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Create it (only accessible by the current user) */
+ rc = RTDirCreateUniqueNumbered(szDropDir, sizeof(szDropDir), RTFS_UNIX_IRWXU, 3, '-');
+ if (RT_SUCCESS(rc))
+ {
+ RTDIR hDir;
+ rc = RTDirOpen(&hDir, szDropDir);
+ if (RT_SUCCESS(rc))
+ {
+ pDF->pszPathAbs = RTStrDup(szDropDir);
+ AssertPtrBreakStmt(pDF->pszPathAbs, rc = VERR_NO_MEMORY);
+ pDF->m_hDir = hDir;
+ pDF->m_fOpen = fFlags;
+ }
+ }
+
+ } while (0);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Opens (creates) the dropped files directory in the system's temp directory.
+ *
+ * @returns VBox status code.
+ * @param pDF DnD Dropped Files to open.
+ * @param fFlags Dropped files flags to use for this directory.
+ */
+int DnDDroppedFilesOpenTemp(PDNDDROPPEDFILES pDF, DNDURIDROPPEDFILEFLAGS fFlags)
+{
+ AssertReturn(fFlags == 0, VERR_INVALID_PARAMETER); /* Flags not supported yet. */
+
+ /*
+ * Get the user's temp directory. Don't use the user's root directory (or
+ * something inside it) because we don't know for how long/if the data will
+ * be kept after the guest OS used it.
+ */
+ char szTemp[RTPATH_MAX];
+ int rc = RTPathTemp(szTemp, sizeof(szTemp));
+ if (RT_SUCCESS(rc))
+ rc = DnDDroppedFilesOpenEx(pDF, szTemp, fFlags);
+
+ return rc;
+}
+
+/**
+ * Free's an internal DnD Dropped Files entry.
+ *
+ * @param pEntry Pointer to entry to free. The pointer will be invalid after calling.
+ */
+static void dndDroppedFilesEntryFree(PDNDDROPPEDFILESENTRY pEntry)
+{
+ if (!pEntry)
+ return;
+ RTStrFree(pEntry->pszPath);
+ RTListNodeRemove(&pEntry->Node);
+ RTMemFree(pEntry);
+}
+
+/**
+ * Resets an internal DnD Dropped Files list.
+ *
+ * @param pListAnchor Pointer to list (anchor) to reset.
+ */
+static void dndDroppedFilesResetList(PRTLISTANCHOR pListAnchor)
+{
+ PDNDDROPPEDFILESENTRY pEntryCur, pEntryNext;
+ RTListForEachSafe(pListAnchor, pEntryCur, pEntryNext, DNDDROPPEDFILESENTRY, Node)
+ dndDroppedFilesEntryFree(pEntryCur);
+ Assert(RTListIsEmpty(pListAnchor));
+}
+
+/**
+ * Resets a droppped files directory.
+ *
+ * @returns VBox status code.
+ * @param pDF DnD Dropped Files to reset.
+ * @param fDelete Whether to physically delete the directory and its content
+ * or just clear the internal references.
+ */
+int DnDDroppedFilesReset(PDNDDROPPEDFILES pDF, bool fDelete)
+{
+ int rc = dndDroppedFilesCloseInternal(pDF);
+ if (RT_SUCCESS(rc))
+ {
+ if (fDelete)
+ {
+ rc = DnDDroppedFilesRollback(pDF);
+ }
+ else
+ {
+ dndDroppedFilesResetList(&pDF->m_lstDirs);
+ dndDroppedFilesResetList(&pDF->m_lstFiles);
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Re-opens a droppes files directory.
+ *
+ * @returns VBox status code, or VERR_NOT_FOUND if the dropped files directory has not been opened before.
+ * @param pDF DnD Dropped Files to re-open.
+ */
+int DnDDroppedFilesReopen(PDNDDROPPEDFILES pDF)
+{
+ if (!pDF->pszPathAbs)
+ return VERR_NOT_FOUND;
+
+ return DnDDroppedFilesOpenEx(pDF, pDF->pszPathAbs, pDF->m_fOpen);
+}
+
+/**
+ * Performs a rollback of a dropped files directory.
+ * This cleans the directory by physically deleting all files / directories which have been added before.
+ *
+ * @returns VBox status code.
+ * @param pDF DnD Dropped Files to roll back.
+ */
+int DnDDroppedFilesRollback(PDNDDROPPEDFILES pDF)
+{
+ if (!pDF->pszPathAbs)
+ return VINF_SUCCESS;
+
+ int rc = VINF_SUCCESS;
+
+ /* Rollback by removing any stuff created.
+ * Note: Only remove empty directories, never ever delete
+ * anything recursive here! Steam (tm) knows best ... :-) */
+ int rc2;
+ PDNDDROPPEDFILESENTRY pEntryCur, pEntryNext;
+ RTListForEachSafe(&pDF->m_lstFiles, pEntryCur, pEntryNext, DNDDROPPEDFILESENTRY, Node)
+ {
+ rc2 = RTFileDelete(pEntryCur->pszPath);
+ if (RT_SUCCESS(rc2))
+ dndDroppedFilesEntryFree(pEntryCur);
+ else if (RT_SUCCESS(rc))
+ rc = rc2;
+ /* Keep going. */
+ }
+
+ RTListForEachSafe(&pDF->m_lstDirs, pEntryCur, pEntryNext, DNDDROPPEDFILESENTRY, Node)
+ {
+ rc2 = RTDirRemove(pEntryCur->pszPath);
+ if (RT_SUCCESS(rc2))
+ dndDroppedFilesEntryFree(pEntryCur);
+ else if (RT_SUCCESS(rc))
+ rc = rc2;
+ /* Keep going. */
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc2 = dndDroppedFilesCloseInternal(pDF);
+ if (RT_SUCCESS(rc2))
+ {
+ /* Try to remove the empty root dropped files directory as well.
+ * Might return VERR_DIR_NOT_EMPTY or similar. */
+ rc2 = RTDirRemove(pDF->pszPathAbs);
+ }
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
diff --git a/src/VBox/GuestHost/DragAndDrop/DnDMIME.cpp b/src/VBox/GuestHost/DragAndDrop/DnDMIME.cpp
new file mode 100644
index 00000000..ea7096ed
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDMIME.cpp
@@ -0,0 +1,63 @@
+/* $Id: DnDMIME.cpp $ */
+/** @file
+ * DnD - Path list class.
+ */
+
+/*
+ * Copyright (C) 2014-2022 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_GUEST_DND
+#include <VBox/GuestHost/DragAndDrop.h>
+
+#include <iprt/string.h>
+
+
+/**
+ * Returns whether a given MIME format contains file URLs we can work with or not.
+ *
+ * @returns \c true if the format contains file URLs, \c false if not.
+ * @param pcszFormat MIME format to check.
+ * @param cchFormatMax Maximum characters of MIME format to check.
+ */
+bool DnDMIMEHasFileURLs(const char *pcszFormat, size_t cchFormatMax)
+{
+ /** @todo "text/uri" also an official variant? */
+ return ( RTStrNICmp(pcszFormat, "text/uri-list", cchFormatMax) == 0
+ || RTStrNICmp(pcszFormat, "x-special/gnome-icon-list", cchFormatMax) == 0);
+}
+
+/**
+ * Returns whether a given MIME format needs an own "Dropped Files" directory or not.
+ *
+ * @returns \c true if the format needs an own "Dropped Files" directory, \c false if not.
+ * @param pcszFormat MIME format to check.
+ * @param cchFormatMax Maximum characters of MIME format to check.
+ */
+bool DnDMIMENeedsDropDir(const char *pcszFormat, size_t cchFormatMax)
+{
+ return DnDMIMEHasFileURLs(pcszFormat, cchFormatMax);
+}
+
diff --git a/src/VBox/GuestHost/DragAndDrop/DnDPath.cpp b/src/VBox/GuestHost/DragAndDrop/DnDPath.cpp
new file mode 100644
index 00000000..f57b5cf3
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDPath.cpp
@@ -0,0 +1,212 @@
+/* $Id: DnDPath.cpp $ */
+/** @file
+ * DnD - Path handling.
+ */
+
+/*
+ * Copyright (C) 2014-2022 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_GUEST_DND
+#include <VBox/GuestHost/DragAndDrop.h>
+
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/uri.h>
+
+
+/**
+ * Sanitizes the file name portion of a path so that unsupported characters will be replaced by an underscore ("_").
+ *
+ * @return IPRT status code.
+ * @param pszFileName File name to sanitize.
+ * @param cbFileName Size (in bytes) of file name to sanitize.
+ */
+int DnDPathSanitizeFileName(char *pszFileName, size_t cbFileName)
+{
+ if (!pszFileName) /* No path given? Bail out early. */
+ return VINF_SUCCESS;
+
+ AssertReturn(cbFileName, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+#ifdef RT_OS_WINDOWS
+ RT_NOREF1(cbFileName);
+ /* Replace out characters not allowed on Windows platforms, put in by RTTimeSpecToString(). */
+ /** @todo Use something like RTPathSanitize() if available later some time. */
+ static const RTUNICP s_uszValidRangePairs[] =
+ {
+ ' ', ' ',
+ '(', ')',
+ '-', '.',
+ '0', '9',
+ 'A', 'Z',
+ 'a', 'z',
+ '_', '_',
+ 0xa0, 0xd7af,
+ '\0'
+ };
+
+ ssize_t cReplaced = RTStrPurgeComplementSet(pszFileName, s_uszValidRangePairs, '_' /* chReplacement */);
+ if (cReplaced < 0)
+ rc = VERR_INVALID_UTF8_ENCODING;
+#else
+ RT_NOREF2(pszFileName, cbFileName);
+#endif
+ return rc;
+}
+
+/**
+ * Validates whether a given path matches our set of rules or not.
+ *
+ * Rules:
+ * - An empty path is allowed.
+ * - Dot components ("." or "..") are forbidden.
+ * - If \a fMustExist is \c true, the path either has to be a file or a directory and must exist.
+ * - Symbolic links are forbidden.
+ *
+ * @returns VBox status code.
+ * @param pcszPath Path to validate.
+ * @param fMustExist Whether the path to validate also must exist.
+ * @sa shClTransferValidatePath().
+ */
+int DnDPathValidate(const char *pcszPath, bool fMustExist)
+{
+ if (!pcszPath)
+ return VERR_INVALID_POINTER;
+
+ int rc = VINF_SUCCESS;
+
+ if ( RT_SUCCESS(rc)
+ && !RTStrIsValidEncoding(pcszPath))
+ {
+ rc = VERR_INVALID_UTF8_ENCODING;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && RTStrStr(pcszPath, ".."))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && fMustExist)
+ {
+ RTFSOBJINFO objInfo;
+ rc = RTPathQueryInfo(pcszPath, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ if (!RTDirExists(pcszPath)) /* Path must exist. */
+ rc = VERR_PATH_NOT_FOUND;
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ if (!RTFileExists(pcszPath)) /* File must exist. */
+ rc = VERR_FILE_NOT_FOUND;
+ }
+ else /* Everything else (e.g. symbolic links) are not supported. */
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Converts a DnD path.
+ *
+ * @returns VBox status code.
+ * @param pszPath Path to convert.
+ * @param cbPath Size (in bytes) of path to convert.
+ * @param fFlags Conversion flags of type DNDPATHCONVERT_FLAGS_.
+ */
+int DnDPathConvert(char *pszPath, size_t cbPath, DNDPATHCONVERTFLAGS fFlags)
+{
+ RT_NOREF(cbPath);
+ AssertReturn(!(fFlags & ~DNDPATHCONVERT_FLAGS_VALID_MASK), VERR_INVALID_FLAGS);
+
+ if (fFlags & DNDPATHCONVERT_FLAGS_TO_DOS)
+ RTPathChangeToDosSlashes(pszPath, true /* fForce */);
+ else
+ RTPathChangeToUnixSlashes(pszPath, true /* fForce */);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Rebases an absolute path from an old path base to a new path base.
+ * Note: Does *not* do any path conversion.
+ *
+ * @return IPRT status code.
+ * @param pcszPath Path to rebase.
+ * @param strBaseOld Old base path to rebase from. Optional and can be NULL.
+ * @param strBaseNew New base path to rebase to.
+ * @param ppszPath Where to store the allocated rebased path on success. Needs to be free'd with RTStrFree().
+ */
+int DnDPathRebase(const char *pcszPath, const char *pcszBaseOld, const char *pcszBaseNew,
+ char **ppszPath)
+{
+ AssertPtrReturn(pcszPath, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcszBaseOld, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcszBaseNew, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppszPath, VERR_INVALID_POINTER);
+
+ char szPath[RTPATH_MAX];
+
+ /* Do we need to see if the given path is part of the old base? */
+ size_t idxBase;
+ if ( pcszBaseOld
+ && RTPathStartsWith(pcszPath, pcszBaseOld))
+ {
+ idxBase = strlen(pcszBaseOld);
+ }
+ else
+ idxBase = 0;
+
+ int rc = RTStrCopy(szPath, sizeof(szPath), pcszBaseNew);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPathAppend(szPath, sizeof(szPath), &pcszPath[idxBase]);
+ if (RT_SUCCESS(rc))
+ rc = DnDPathValidate(szPath, false /* fMustExist */);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ char *pszPath = RTStrDup(szPath);
+ if (pszPath)
+ *ppszPath = pszPath;
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
diff --git a/src/VBox/GuestHost/DragAndDrop/DnDTransferList.cpp b/src/VBox/GuestHost/DragAndDrop/DnDTransferList.cpp
new file mode 100644
index 00000000..f7ba75fa
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDTransferList.cpp
@@ -0,0 +1,1295 @@
+/* $Id: DnDTransferList.cpp $ */
+/** @file
+ * DnD - transfer list implemenation.
+ */
+
+/*
+ * Copyright (C) 2014-2022 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
+ */
+
+/**
+ * This implementation is taylored to keeping track of a single DnD transfer by maintaining two separate entities,
+ * namely a list of root entries and a list of (recursive file system) transfer ojects to actually transfer.
+ *
+ * The list of root entries is sent to the target (guest/host) beforehand so that the OS has a data for the
+ * actual drag'n drop operation to work with. This also contains required header data like total number of
+ * objects or total bytes being received.
+ *
+ * The list of transfer objects only is needed in order to sending data from the source to the target.
+ * Currently there is no particular ordering implemented for the transfer object list; it depends on IPRT's RTDirRead().
+ *
+ * The target must not know anything about the actual (absolute) path the root entries are coming from
+ * due to security reasons. Those root entries then can be re-based on the target to desired location there.
+ *
+ * All data handling internally is done in the so-called "transport" format, that is, non-URI (regular) paths
+ * with the "/" as path separator. From/to URI conversion is provided for convenience only.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_GUEST_DND
+#include <VBox/GuestHost/DragAndDrop.h>
+
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/fs.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/symlink.h>
+#include <iprt/uri.h>
+
+#include <VBox/log.h>
+
+
+/*********************************************************************************************************************************
+* Prototypes *
+*********************************************************************************************************************************/
+static int dndTransferListSetRootPath(PDNDTRANSFERLIST pList, const char *pcszRootPathAbs);
+
+static int dndTransferListRootEntryAdd(PDNDTRANSFERLIST pList, const char *pcszRoot);
+static void dndTransferListRootEntryFree(PDNDTRANSFERLIST pList, PDNDTRANSFERLISTROOT pRootObj);
+
+static int dndTransferListObjAdd(PDNDTRANSFERLIST pList, const char *pcszSrcAbs, RTFMODE fMode, DNDTRANSFERLISTFLAGS fFlags);
+static void dndTransferListObjFree(PDNDTRANSFERLIST pList, PDNDTRANSFEROBJECT pLstObj);
+
+
+/** The size of the directory entry buffer we're using. */
+#define DNDTRANSFERLIST_DIRENTRY_BUF_SIZE (sizeof(RTDIRENTRYEX) + RTPATH_MAX)
+
+
+/**
+ * Initializes a transfer list, internal version.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to initialize.
+ * @param pcszRootPathAbs Absolute root path to use for this list. Optional and can be NULL.
+ */
+static int dndTransferListInitInternal(PDNDTRANSFERLIST pList, const char *pcszRootPathAbs)
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ /* pcszRootPathAbs is optional. */
+
+ if (pList->pszPathRootAbs) /* Already initialized? */
+ return VERR_WRONG_ORDER;
+
+ pList->pszPathRootAbs = NULL;
+
+ RTListInit(&pList->lstRoot);
+ pList->cRoots = 0;
+
+ RTListInit(&pList->lstObj);
+ pList->cObj = 0;
+ pList->cbObjTotal = 0;
+
+ if (pcszRootPathAbs)
+ return dndTransferListSetRootPath(pList, pcszRootPathAbs);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Initializes a transfer list, extended version.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to initialize.
+ * @param pcszRootPathAbs Absolute root path to use for this list.
+ * @param enmFmt Format of \a pcszRootPathAbs.
+ */
+int DnDTransferListInitEx(PDNDTRANSFERLIST pList, const char *pcszRootPathAbs, DNDTRANSFERLISTFMT enmFmt)
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcszRootPathAbs, VERR_INVALID_POINTER);
+ AssertReturn(*pcszRootPathAbs, VERR_INVALID_PARAMETER);
+
+ int rc;
+
+ if (enmFmt == DNDTRANSFERLISTFMT_URI)
+ {
+ char *pszPath;
+ rc = RTUriFilePathEx(pcszRootPathAbs, RTPATH_STR_F_STYLE_UNIX, &pszPath, 0 /*cbPath*/, NULL /*pcchPath*/);
+ if (RT_SUCCESS(rc))
+ {
+ rc = dndTransferListInitInternal(pList, pszPath);
+ RTStrFree(pszPath);
+ }
+ }
+ else
+ rc = dndTransferListInitInternal(pList, pcszRootPathAbs);
+
+ return rc;
+}
+
+/**
+ * Initializes a transfer list.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to initialize.
+ */
+int DnDTransferListInit(PDNDTRANSFERLIST pList)
+{
+ return dndTransferListInitInternal(pList, NULL /* pcszRootPathAbs */);
+}
+
+/**
+ * Destroys a transfer list.
+ *
+ * @param pList Transfer list to destroy.
+ */
+void DnDTransferListDestroy(PDNDTRANSFERLIST pList)
+{
+ if (!pList)
+ return;
+
+ DnDTransferListReset(pList);
+
+ RTStrFree(pList->pszPathRootAbs);
+ pList->pszPathRootAbs = NULL;
+}
+
+/**
+ * Initializes a transfer list and sets the root path.
+ *
+ * Convenience function which calls dndTransferListInitInternal() if not initialized already.
+ *
+ * @returns VBox status code.
+ * @param pList List to determine root path for.
+ * @param pcszRootPathAbs Root path to use.
+ */
+static int dndTransferInitAndSetRoot(PDNDTRANSFERLIST pList, const char *pcszRootPathAbs)
+{
+ int rc;
+
+ if (!pList->pszPathRootAbs)
+ {
+ rc = dndTransferListInitInternal(pList, pcszRootPathAbs);
+ AssertRCReturn(rc, rc);
+
+ LogRel2(("DnD: Determined root path is '%s'\n", pList->pszPathRootAbs));
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ return rc;
+}
+
+/**
+ * Resets a transfer list to its initial state.
+ *
+ * @param pList Transfer list to reset.
+ */
+void DnDTransferListReset(PDNDTRANSFERLIST pList)
+{
+ AssertPtrReturnVoid(pList);
+
+ if (!pList->pszPathRootAbs)
+ return;
+
+ RTStrFree(pList->pszPathRootAbs);
+ pList->pszPathRootAbs = NULL;
+
+ PDNDTRANSFERLISTROOT pRootCur, pRootNext;
+ RTListForEachSafe(&pList->lstRoot, pRootCur, pRootNext, DNDTRANSFERLISTROOT, Node)
+ dndTransferListRootEntryFree(pList, pRootCur);
+ Assert(RTListIsEmpty(&pList->lstRoot));
+
+ PDNDTRANSFEROBJECT pObjCur, pObjNext;
+ RTListForEachSafe(&pList->lstObj, pObjCur, pObjNext, DNDTRANSFEROBJECT, Node)
+ dndTransferListObjFree(pList, pObjCur);
+ Assert(RTListIsEmpty(&pList->lstObj));
+
+ Assert(pList->cRoots == 0);
+ Assert(pList->cObj == 0);
+
+ pList->cbObjTotal = 0;
+}
+
+/**
+ * Adds a single transfer object entry to a transfer List.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to add entry to.
+ * @param pcszSrcAbs Absolute source path (local) to use.
+ * @param fMode File mode of entry to add.
+ * @param fFlags Transfer list flags to use for appending.
+ */
+static int dndTransferListObjAdd(PDNDTRANSFERLIST pList, const char *pcszSrcAbs, RTFMODE fMode, DNDTRANSFERLISTFLAGS fFlags)
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcszSrcAbs, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pcszSrcAbs=%s, fMode=%#x, fFlags=0x%x\n", pcszSrcAbs, fMode, fFlags));
+
+ int rc = VINF_SUCCESS;
+
+ if ( !RTFS_IS_FILE(fMode)
+ && !RTFS_IS_DIRECTORY(fMode))
+ /** @todo Symlinks not allowed. */
+ {
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Calculate the path to add as the destination path to our URI object. */
+ const size_t idxPathToAdd = strlen(pList->pszPathRootAbs);
+ AssertReturn(strlen(pcszSrcAbs) > idxPathToAdd, VERR_INVALID_PARAMETER); /* Should never happen (tm). */
+
+ PDNDTRANSFEROBJECT pObj = (PDNDTRANSFEROBJECT)RTMemAllocZ(sizeof(DNDTRANSFEROBJECT));
+ if (pObj)
+ {
+ const bool fIsFile = RTFS_IS_FILE(fMode);
+
+ rc = DnDTransferObjectInitEx(pObj, fIsFile ? DNDTRANSFEROBJTYPE_FILE : DNDTRANSFEROBJTYPE_DIRECTORY,
+ pList->pszPathRootAbs, &pcszSrcAbs[idxPathToAdd]);
+ if (RT_SUCCESS(rc))
+ {
+ if (fIsFile)
+ rc = DnDTransferObjectOpen(pObj,
+ RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, /** @todo Add a standard fOpen mode for this list. */
+ 0 /* fMode */, DNDTRANSFEROBJECT_FLAGS_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ RTListAppend(&pList->lstObj, &pObj->Node);
+
+ pList->cObj++;
+ if (fIsFile)
+ pList->cbObjTotal += DnDTransferObjectGetSize(pObj);
+
+ if ( fIsFile
+ && !(fFlags & DNDTRANSFERLIST_FLAGS_KEEP_OPEN)) /* Shall we keep the file open while being added to this list? */
+ rc = DnDTransferObjectClose(pObj);
+ }
+
+ if (RT_FAILURE(rc))
+ DnDTransferObjectDestroy(pObj);
+ }
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pObj);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("DnD: Adding entry '%s' of type %#x failed with rc=%Rrc\n", pcszSrcAbs, fMode & RTFS_TYPE_MASK, rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Frees an internal DnD transfer list object.
+ *
+ * @param pList Transfer list to free object for.
+ * @param pLstObj transfer list object to free. The pointer will be invalid after calling.
+ */
+static void dndTransferListObjFree(PDNDTRANSFERLIST pList, PDNDTRANSFEROBJECT pObj)
+{
+ if (!pObj)
+ return;
+
+ DnDTransferObjectDestroy(pObj);
+ RTListNodeRemove(&pObj->Node);
+ RTMemFree(pObj);
+
+ AssertReturnVoid(pList->cObj);
+ pList->cObj--;
+}
+
+/**
+ * Helper routine for handling adding sub directories.
+ *
+ * @return IPRT status code.
+ * @param pList transfer list to add found entries to.
+ * @param pszDir Pointer to the directory buffer.
+ * @param cchDir The length of pszDir in pszDir.
+ * @param pDirEntry Pointer to the directory entry.
+ * @param fFlags Flags of type DNDTRANSFERLISTFLAGS.
+ */
+static int dndTransferListAppendPathRecursiveSub(PDNDTRANSFERLIST pList,
+ char *pszDir, size_t cchDir, PRTDIRENTRYEX pDirEntry,
+ DNDTRANSFERLISTFLAGS fFlags)
+
+{
+ Assert(cchDir > 0); Assert(pszDir[cchDir] == '\0');
+
+ /* Make sure we've got some room in the path, to save us extra work further down. */
+ if (cchDir + 3 >= RTPATH_MAX)
+ return VERR_BUFFER_OVERFLOW;
+
+ /* Open directory. */
+ RTDIR hDir;
+ int rc = RTDirOpen(&hDir, pszDir);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Ensure we've got a trailing slash (there is space for it see above). */
+ if (!RTPATH_IS_SEP(pszDir[cchDir - 1]))
+ {
+ pszDir[cchDir++] = RTPATH_SLASH;
+ pszDir[cchDir] = '\0';
+ }
+
+ rc = dndTransferListObjAdd(pList, pszDir, pDirEntry->Info.Attr.fMode, fFlags);
+ AssertRCReturn(rc, rc);
+
+ LogFlowFunc(("pszDir=%s\n", pszDir));
+
+ /*
+ * Process the files and subdirs.
+ */
+ for (;;)
+ {
+ /* Get the next directory. */
+ size_t cbDirEntry = DNDTRANSFERLIST_DIRENTRY_BUF_SIZE;
+ rc = RTDirReadEx(hDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Check length. */
+ if (pDirEntry->cbName + cchDir + 3 >= RTPATH_MAX)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_SYMLINK:
+ {
+ if (!(fFlags & DNDTRANSFERLIST_FLAGS_RESOLVE_SYMLINKS))
+ break;
+ RT_FALL_THRU();
+ }
+ case RTFS_TYPE_DIRECTORY:
+ {
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ continue;
+
+ memcpy(&pszDir[cchDir], pDirEntry->szName, pDirEntry->cbName + 1);
+ int rc2 = dndTransferListAppendPathRecursiveSub(pList, pszDir, cchDir + pDirEntry->cbName, pDirEntry, fFlags);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ memcpy(&pszDir[cchDir], pDirEntry->szName, pDirEntry->cbName + 1);
+ rc = dndTransferListObjAdd(pList, pszDir, pDirEntry->Info.Attr.fMode, fFlags);
+ break;
+ }
+
+ default:
+ {
+
+ break;
+ }
+ }
+ }
+
+ if (rc == VERR_NO_MORE_FILES) /* Done reading current directory? */
+ {
+ rc = VINF_SUCCESS;
+ }
+ else if (RT_FAILURE(rc))
+ LogRel(("DnD: Error while adding files recursively, rc=%Rrc\n", rc));
+
+ int rc2 = RTDirClose(hDir);
+ if (RT_FAILURE(rc2))
+ {
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return rc;
+}
+
+/**
+ * Appends a native system path recursively by adding these entries as transfer objects.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to add found entries to.
+ * @param pcszPathAbs Absolute path to add.
+ * @param fFlags Flags of type DNDTRANSFERLISTFLAGS.
+ */
+static int dndTransferListAppendDirectoryRecursive(PDNDTRANSFERLIST pList,
+ const char *pcszPathAbs, DNDTRANSFERLISTFLAGS fFlags)
+{
+ char szPathAbs[RTPATH_MAX];
+ int rc = RTStrCopy(szPathAbs, sizeof(szPathAbs), pcszPathAbs);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ union
+ {
+ uint8_t abPadding[DNDTRANSFERLIST_DIRENTRY_BUF_SIZE];
+ RTDIRENTRYEX DirEntry;
+ } uBuf;
+
+ const size_t cchPathAbs = RTStrNLen(szPathAbs, RTPATH_MAX);
+ AssertReturn(cchPathAbs, VERR_BUFFER_OVERFLOW);
+
+ /* Use the directory entry to hand-in the directorie's information. */
+ rc = RTPathQueryInfo(pcszPathAbs, &uBuf.DirEntry.Info, RTFSOBJATTRADD_NOTHING);
+ AssertRCReturn(rc, rc);
+
+ return dndTransferListAppendPathRecursiveSub(pList, szPathAbs, cchPathAbs, &uBuf.DirEntry, fFlags);
+}
+
+/**
+ * Helper function for appending a local directory to a DnD transfer list.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to return total number of root entries for.
+ * @param pszPathAbs Absolute path of directory to append.
+ * @param cbPathAbs Size (in bytes) of absolute path of directory to append.
+ * @param pObjInfo Pointer to directory object info to append.
+ * @param fFlags Transfer list flags to use for appending.
+ */
+static int dndTransferListAppendDirectory(PDNDTRANSFERLIST pList, char* pszPathAbs, size_t cbPathAbs,
+ PRTFSOBJINFO pObjInfo, DNDTRANSFERLISTFLAGS fFlags)
+{
+ const size_t cchPathRoot = RTStrNLen(pList->pszPathRootAbs, RTPATH_MAX);
+ AssertReturn(cchPathRoot, VERR_INVALID_PARAMETER);
+
+ const size_t cchPathAbs = RTPathEnsureTrailingSeparator(pszPathAbs, sizeof(cbPathAbs));
+ AssertReturn(cchPathAbs, VERR_BUFFER_OVERFLOW);
+ AssertReturn(cchPathAbs >= cchPathRoot, VERR_BUFFER_UNDERFLOW);
+
+ const bool fPathIsRoot = cchPathAbs == cchPathRoot;
+
+ int rc;
+
+ if (!fPathIsRoot)
+ {
+ rc = dndTransferListObjAdd(pList, pszPathAbs, pObjInfo->Attr.fMode, fFlags);
+ AssertRCReturn(rc, rc);
+ }
+
+ RTDIR hDir;
+ rc = RTDirOpen(&hDir, pszPathAbs);
+ AssertRCReturn(rc, rc);
+
+ for (;;)
+ {
+ /* Get the next entry. */
+ RTDIRENTRYEX dirEntry;
+ rc = RTDirReadEx(hDir, &dirEntry, NULL, RTFSOBJATTRADD_UNIX,
+ RTPATH_F_ON_LINK /** @todo No symlinks yet. */);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTDirEntryExIsStdDotLink(&dirEntry))
+ continue;
+
+ /* Check length. */
+ if (dirEntry.cbName + cchPathAbs + 3 >= cbPathAbs)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ /* Append the directory entry to our absolute path. */
+ memcpy(&pszPathAbs[cchPathAbs], dirEntry.szName, dirEntry.cbName + 1 /* Include terminator */);
+
+ LogFlowFunc(("szName=%s, pszPathAbs=%s\n", dirEntry.szName, pszPathAbs));
+
+ switch (dirEntry.Info.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ if (fFlags & DNDTRANSFERLIST_FLAGS_RECURSIVE)
+ rc = dndTransferListAppendDirectoryRecursive(pList, pszPathAbs, fFlags);
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ rc = dndTransferListObjAdd(pList, pszPathAbs, dirEntry.Info.Attr.fMode, fFlags);
+ break;
+ }
+
+ default:
+ /* Silently skip everything else. */
+ break;
+ }
+
+ if ( RT_SUCCESS(rc)
+ /* Make sure to add a root entry if we're processing the root path at the moment. */
+ && fPathIsRoot)
+ {
+ rc = dndTransferListRootEntryAdd(pList, pszPathAbs);
+ }
+ }
+ else if (rc == VERR_NO_MORE_FILES)
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+ else
+ break;
+ }
+
+ RTDirClose(hDir);
+ return rc;
+}
+
+/**
+ * Appends a native path to a DnD transfer list.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to append native path to.
+ * @param pcszPath Path (native) to append.
+ * @param fFlags Transfer list flags to use for appending.
+ */
+static int dndTransferListAppendPathNative(PDNDTRANSFERLIST pList, const char *pcszPath, DNDTRANSFERLISTFLAGS fFlags)
+{
+ /* We don't want to have a relative directory here. */
+ if (!RTPathStartsWithRoot(pcszPath))
+ return VERR_INVALID_PARAMETER;
+
+ int rc = DnDPathValidate(pcszPath, false /* fMustExist */);
+ AssertRCReturn(rc, rc);
+
+ char szPathAbs[RTPATH_MAX];
+ size_t cbPathAbs = sizeof(szPathAbs);
+ rc = RTStrCopy(szPathAbs, cbPathAbs, pcszPath);
+ AssertRCReturn(rc, rc);
+
+ size_t cchPathAbs = RTStrNLen(szPathAbs, cbPathAbs);
+ AssertReturn(cchPathAbs, VERR_INVALID_PARAMETER);
+
+ /* Convert path to transport style. */
+ rc = DnDPathConvert(szPathAbs, cbPathAbs, DNDPATHCONVERT_FLAGS_TRANSPORT);
+ if (RT_SUCCESS(rc))
+ {
+ /* Make sure the path has the same root path as our list. */
+ if (RTPathStartsWith(szPathAbs, pList->pszPathRootAbs))
+ {
+ RTFSOBJINFO objInfo;
+ rc = RTPathQueryInfo(szPathAbs, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ const uint32_t fType = objInfo.Attr.fMode & RTFS_TYPE_MASK;
+
+ if ( RTFS_IS_DIRECTORY(fType)
+ || RTFS_IS_FILE(fType))
+ {
+ if (RTFS_IS_DIRECTORY(fType))
+ {
+ cchPathAbs = RTPathEnsureTrailingSeparator(szPathAbs, cbPathAbs);
+ AssertReturn(cchPathAbs, VERR_BUFFER_OVERFLOW);
+ }
+
+ const size_t cchPathRoot = RTStrNLen(pList->pszPathRootAbs, RTPATH_MAX);
+ AssertStmt(cchPathRoot, rc = VERR_INVALID_PARAMETER);
+
+ if ( RT_SUCCESS(rc)
+ && cchPathAbs > cchPathRoot)
+ rc = dndTransferListRootEntryAdd(pList, szPathAbs);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ if (RT_SUCCESS(rc))
+ {
+ switch (fType)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ rc = dndTransferListAppendDirectory(pList, szPathAbs, cbPathAbs, &objInfo, fFlags);
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ rc = dndTransferListObjAdd(pList, szPathAbs, objInfo.Attr.fMode, fFlags);
+ break;
+ }
+
+ default:
+ AssertFailed();
+ break;
+ }
+ }
+ }
+ /* On UNIX-y OSes RTPathQueryInfo() returns VERR_FILE_NOT_FOUND in some cases
+ * so tweak this to make it uniform to Windows. */
+ else if (rc == VERR_FILE_NOT_FOUND)
+ rc = VERR_PATH_NOT_FOUND;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("DnD: Adding native path '%s' failed with rc=%Rrc\n", pcszPath, rc));
+
+ return rc;
+}
+
+/**
+ * Appends an URI path to a DnD transfer list.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to append native path to.
+ * @param pcszPath URI path to append.
+ * @param fFlags Transfer list flags to use for appending.
+ */
+static int dndTransferListAppendPathURI(PDNDTRANSFERLIST pList, const char *pcszPath, DNDTRANSFERLISTFLAGS fFlags)
+{
+ RT_NOREF(fFlags);
+
+ /* Query the path component of a file URI. If this hasn't a
+ * file scheme, NULL is returned. */
+ char *pszPath;
+ int rc = RTUriFilePathEx(pcszPath, RTPATH_STR_F_STYLE_UNIX, &pszPath, 0 /*cbPath*/, NULL /*pcchPath*/);
+ if (RT_SUCCESS(rc))
+ {
+ rc = dndTransferListAppendPathNative(pList, pszPath, fFlags);
+ RTStrFree(pszPath);
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("DnD: Adding URI path '%s' failed with rc=%Rrc\n", pcszPath, rc));
+
+ return rc;
+}
+
+/**
+ * Appends a single path to a transfer list.
+ *
+ * @returns VBox status code. VERR_NOT_SUPPORTED if the path is not supported.
+ * @param pList Transfer list to append to.
+ * @param enmFmt Format of \a pszPaths to append.
+ * @param pcszPath Path to append. Must be part of the list's set root path.
+ * @param fFlags Transfer list flags to use for appending.
+ */
+int DnDTransferListAppendPath(PDNDTRANSFERLIST pList,
+ DNDTRANSFERLISTFMT enmFmt, const char *pcszPath, DNDTRANSFERLISTFLAGS fFlags)
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcszPath, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~DNDTRANSFERLIST_FLAGS_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(!(fFlags & DNDTRANSFERLIST_FLAGS_RESOLVE_SYMLINKS), VERR_NOT_SUPPORTED);
+
+ int rc;
+
+ switch (enmFmt)
+ {
+ case DNDTRANSFERLISTFMT_NATIVE:
+ rc = dndTransferListAppendPathNative(pList, pcszPath, fFlags);
+ break;
+
+ case DNDTRANSFERLISTFMT_URI:
+ rc = dndTransferListAppendPathURI(pList, pcszPath, fFlags);
+ break;
+
+ default:
+ AssertFailedStmt(rc = VERR_NOT_SUPPORTED);
+ break; /* Never reached */
+ }
+
+ return rc;
+}
+
+/**
+ * Appends native paths to a transfer list.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to append to.
+ * @param enmFmt Format of \a pszPaths to append.
+ * @param pszPaths Buffer of paths to append.
+ * @param cbPaths Size (in bytes) of buffer of paths to append.
+ * @param pcszSeparator Separator string to use.
+ * @param fFlags Transfer list flags to use for appending.
+ */
+int DnDTransferListAppendPathsFromBuffer(PDNDTRANSFERLIST pList,
+ DNDTRANSFERLISTFMT enmFmt, const char *pszPaths, size_t cbPaths,
+ const char *pcszSeparator, DNDTRANSFERLISTFLAGS fFlags)
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPaths, VERR_INVALID_POINTER);
+ AssertReturn(cbPaths, VERR_INVALID_PARAMETER);
+
+ char **papszPaths = NULL;
+ size_t cPaths = 0;
+ int rc = RTStrSplit(pszPaths, cbPaths, pcszSeparator, &papszPaths, &cPaths);
+ if (RT_SUCCESS(rc))
+ rc = DnDTransferListAppendPathsFromArray(pList, enmFmt, papszPaths, cPaths, fFlags);
+
+ for (size_t i = 0; i < cPaths; ++i)
+ RTStrFree(papszPaths[i]);
+ RTMemFree(papszPaths);
+
+ return rc;
+}
+
+/**
+ * Appends paths to a transfer list.
+ *
+ * @returns VBox status code. Will return VERR_INVALID_PARAMETER if a common root path could not be found.
+ * @param pList Transfer list to append path to.
+ * @param enmFmt Format of \a papcszPaths to append.
+ * @param papcszPaths Array of paths to append.
+ * @param cPaths Number of paths in \a papcszPaths to append.
+ * @param fFlags Transfer list flags to use for appending.
+ */
+int DnDTransferListAppendPathsFromArray(PDNDTRANSFERLIST pList,
+ DNDTRANSFERLISTFMT enmFmt,
+ const char * const *papcszPaths, size_t cPaths, DNDTRANSFERLISTFLAGS fFlags)
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrReturn(papcszPaths, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~DNDTRANSFERLIST_FLAGS_VALID_MASK), VERR_INVALID_FLAGS);
+
+ int rc = VINF_SUCCESS;
+
+ if (!cPaths) /* Nothing to add? Bail out. */
+ return VINF_SUCCESS;
+
+ char **papszPathsTmp = NULL;
+
+ /* If URI data is being handed in, extract the paths first. */
+ if (enmFmt == DNDTRANSFERLISTFMT_URI)
+ {
+ papszPathsTmp = (char **)RTMemAlloc(sizeof(char *) * cPaths);
+ if (papszPathsTmp)
+ {
+ for (size_t i = 0; i < cPaths; i++)
+ papszPathsTmp[i] = RTUriFilePath(papcszPaths[i]);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* If we don't have a root path set, try to find the common path of all handed-in paths. */
+ if (!pList->pszPathRootAbs)
+ {
+ /* Can we work on the unmodified, handed-in data or do we need to use our temporary paths? */
+ const char * const *papszPathTmp = enmFmt == DNDTRANSFERLISTFMT_NATIVE
+ ? papcszPaths : papszPathsTmp;
+
+ size_t cchRootPath = 0; /* Length of root path in chars. */
+ if (cPaths > 1)
+ {
+ cchRootPath = RTPathFindCommon(cPaths, papszPathTmp);
+ }
+ else
+ cchRootPath = RTPathParentLength(papszPathTmp[0]);
+
+ if (cchRootPath)
+ {
+ /* Just use the first path in the array as the reference. */
+ char *pszRootPath = RTStrDupN(papszPathTmp[0], cchRootPath);
+ if (pszRootPath)
+ {
+ rc = dndTransferInitAndSetRoot(pList, pszRootPath);
+ RTStrFree(pszRootPath);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Add all paths to the list.
+ */
+ for (size_t i = 0; i < cPaths; i++)
+ {
+ const char *pcszPath = enmFmt == DNDTRANSFERLISTFMT_NATIVE
+ ? papcszPaths[i] : papszPathsTmp[i];
+ rc = DnDTransferListAppendPath(pList, DNDTRANSFERLISTFMT_NATIVE, pcszPath, fFlags);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("DnD: Adding path '%s' (format %#x, root '%s') to transfer list failed with %Rrc\n",
+ pcszPath, enmFmt, pList->pszPathRootAbs ? pList->pszPathRootAbs : "<None>", rc));
+ break;
+ }
+ }
+ }
+
+ if (papszPathsTmp)
+ {
+ for (size_t i = 0; i < cPaths; i++)
+ RTStrFree(papszPathsTmp[i]);
+ RTMemFree(papszPathsTmp);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Appends the root entries for a transfer list.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to append to.
+ * @param enmFmt Format of \a pszPaths to append.
+ * @param pszPaths Buffer of paths to append.
+ * @param cbPaths Size (in bytes) of buffer of paths to append.
+ * @param pcszSeparator Separator string to use.
+ * @param fFlags Transfer list flags to use for appending.
+ */
+int DnDTransferListAppendRootsFromBuffer(PDNDTRANSFERLIST pList,
+ DNDTRANSFERLISTFMT enmFmt, const char *pszPaths, size_t cbPaths,
+ const char *pcszSeparator, DNDTRANSFERLISTFLAGS fFlags)
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPaths, VERR_INVALID_POINTER);
+ AssertReturn(cbPaths, VERR_INVALID_PARAMETER);
+
+ char **papszPaths = NULL;
+ size_t cPaths = 0;
+ int rc = RTStrSplit(pszPaths, cbPaths, pcszSeparator, &papszPaths, &cPaths);
+ if (RT_SUCCESS(rc))
+ rc = DnDTransferListAppendRootsFromArray(pList, enmFmt, papszPaths, cPaths, fFlags);
+
+ for (size_t i = 0; i < cPaths; ++i)
+ RTStrFree(papszPaths[i]);
+ RTMemFree(papszPaths);
+
+ return rc;
+}
+
+/**
+ * Appends root entries to a transfer list.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to append root entries to.
+ * @param enmFmt Format of \a papcszPaths to append.
+ * @param papcszPaths Array of paths to append.
+ * @param cPaths Number of paths in \a papcszPaths to append.
+ * @param fFlags Transfer list flags to use for appending.
+ */
+int DnDTransferListAppendRootsFromArray(PDNDTRANSFERLIST pList,
+ DNDTRANSFERLISTFMT enmFmt,
+ const char * const *papcszPaths, size_t cPaths, DNDTRANSFERLISTFLAGS fFlags)
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrReturn(papcszPaths, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~DNDTRANSFERLIST_FLAGS_VALID_MASK), VERR_INVALID_FLAGS);
+
+ AssertMsgReturn(pList->pszPathRootAbs, ("Root path not set yet\n"), VERR_WRONG_ORDER);
+
+ int rc = VINF_SUCCESS;
+
+ if (!cPaths) /* Nothing to add? Bail out. */
+ return VINF_SUCCESS;
+
+ char **papszPathsTmp = NULL;
+
+ /* If URI data is being handed in, extract the paths first. */
+ if (enmFmt == DNDTRANSFERLISTFMT_URI)
+ {
+ papszPathsTmp = (char **)RTMemAlloc(sizeof(char *) * cPaths);
+ if (papszPathsTmp)
+ {
+ for (size_t i = 0; i < cPaths; i++)
+ papszPathsTmp[i] = RTUriFilePath(papcszPaths[i]);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ char szPath[RTPATH_MAX];
+
+ /*
+ * Add all root entries to the root list.
+ */
+ for (size_t i = 0; i < cPaths; i++)
+ {
+ const char *pcszPath = enmFmt == DNDTRANSFERLISTFMT_NATIVE
+ ? papcszPaths[i] : papszPathsTmp[i];
+
+ rc = RTPathJoin(szPath, sizeof(szPath), pList->pszPathRootAbs, pcszPath);
+ AssertRCBreak(rc);
+
+ rc = DnDPathConvert(szPath, sizeof(szPath), DNDPATHCONVERT_FLAGS_TRANSPORT);
+ AssertRCBreak(rc);
+
+ rc = dndTransferListRootEntryAdd(pList, szPath);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("DnD: Adding root entry '%s' (format %#x, root '%s') to transfer list failed with %Rrc\n",
+ szPath, enmFmt, pList->pszPathRootAbs, rc));
+ break;
+ }
+ }
+
+ if (papszPathsTmp)
+ {
+ for (size_t i = 0; i < cPaths; i++)
+ RTStrFree(papszPathsTmp[i]);
+ RTMemFree(papszPathsTmp);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns the first transfer object in a list.
+ *
+ * @returns Pointer to transfer object if found, or NULL if not found.
+ * @param pList Transfer list to get first transfer object from.
+ */
+PDNDTRANSFEROBJECT DnDTransferListObjGetFirst(PDNDTRANSFERLIST pList)
+{
+ AssertPtrReturn(pList, NULL);
+
+ return RTListGetFirst(&pList->lstObj, DNDTRANSFEROBJECT, Node);
+}
+
+/**
+ * Removes an object from a transfer list, internal version.
+ *
+ * @param pList Transfer list to remove object from.
+ * @param pObj Object to remove. The object will be free'd and the pointer is invalid after calling.
+ */
+static void dndTransferListObjRemoveInternal(PDNDTRANSFERLIST pList, PDNDTRANSFEROBJECT pObj)
+{
+ AssertPtrReturnVoid(pList);
+ AssertPtrReturnVoid(pObj);
+
+ /** @todo Validate if \a pObj is part of \a pList. */
+
+ uint64_t cbSize = DnDTransferObjectGetSize(pObj);
+ Assert(pList->cbObjTotal >= cbSize);
+ pList->cbObjTotal -= cbSize; /* Adjust total size. */
+
+ dndTransferListObjFree(pList, pObj);
+}
+
+/**
+ * Removes an object from a transfer list.
+ *
+ * @param pList Transfer list to remove object from.
+ * @param pObj Object to remove. The object will be free'd and the pointer is invalid after calling.
+ */
+void DnDTransferListObjRemove(PDNDTRANSFERLIST pList, PDNDTRANSFEROBJECT pObj)
+{
+ return dndTransferListObjRemoveInternal(pList, pObj);
+}
+
+/**
+ * Removes the first DnD transfer object from a transfer list.
+ *
+ * @param pList Transfer list to remove first entry for.
+ */
+void DnDTransferListObjRemoveFirst(PDNDTRANSFERLIST pList)
+{
+ AssertPtrReturnVoid(pList);
+
+ if (!pList->cObj)
+ return;
+
+ PDNDTRANSFEROBJECT pObj = RTListGetFirst(&pList->lstObj, DNDTRANSFEROBJECT, Node);
+ AssertPtr(pObj);
+
+ dndTransferListObjRemoveInternal(pList, pObj);
+}
+
+/**
+ * Returns all root entries of a transfer list as a string.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to return root paths for.
+ * @param pcszPathBase Root path to use as a base path. If NULL, the list's absolute root path will be used (if any).
+ * @param pcszSeparator Separator to use for separating the root entries.
+ * @param ppszBuffer Where to return the allocated string on success. Needs to be free'd with RTStrFree().
+ * @param pcbBuffer Where to return the size (in bytes) of the allocated string on success, including terminator.
+ */
+int DnDTransferListGetRootsEx(PDNDTRANSFERLIST pList,
+ DNDTRANSFERLISTFMT enmFmt, const char *pcszPathBase, const char *pcszSeparator,
+ char **ppszBuffer, size_t *pcbBuffer)
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ /* pcszPathBase can be NULL. */
+ AssertPtrReturn(pcszSeparator, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppszBuffer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbBuffer, VERR_INVALID_POINTER);
+
+ char *pszString = NULL;
+ size_t cchString = 0;
+
+ const size_t cchSep = RTStrNLen(pcszSeparator, RTPATH_MAX);
+
+ /* Find out which root path to use. */
+ const char *pcszPathRootTmp = pcszPathBase ? pcszPathBase : pList->pszPathRootAbs;
+ /* pcszPathRootTmp can be NULL */
+
+ LogFlowFunc(("Using root path '%s'\n", pcszPathRootTmp ? pcszPathRootTmp : "<None>"));
+
+ int rc = DnDPathValidate(pcszPathRootTmp, false /* fMustExist */);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ char szPath[RTPATH_MAX];
+
+ PDNDTRANSFERLISTROOT pRoot;
+ RTListForEach(&pList->lstRoot, pRoot, DNDTRANSFERLISTROOT, Node)
+ {
+ if (pcszPathRootTmp)
+ {
+ rc = RTStrCopy(szPath, sizeof(szPath), pcszPathRootTmp);
+ AssertRCBreak(rc);
+ }
+
+ rc = RTPathAppend(szPath, sizeof(szPath), pRoot->pszPathRoot);
+ AssertRCBreak(rc);
+
+ if (enmFmt == DNDTRANSFERLISTFMT_URI)
+ {
+ char *pszPathURI = RTUriFileCreate(szPath);
+ AssertPtrBreakStmt(pszPathURI, rc = VERR_NO_MEMORY);
+ rc = RTStrAAppend(&pszString, pszPathURI);
+ cchString += RTStrNLen(pszPathURI, RTPATH_MAX);
+ RTStrFree(pszPathURI);
+ AssertRCBreak(rc);
+ }
+ else /* Native */
+ {
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ /* Convert paths to native path style. */
+ rc = DnDPathConvert(szPath, sizeof(szPath), DNDPATHCONVERT_FLAGS_TO_DOS);
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrAAppend(&pszString, szPath);
+ AssertRCBreak(rc);
+
+ cchString += RTStrNLen(szPath, RTPATH_MAX);
+ }
+ }
+
+ rc = RTStrAAppend(&pszString, pcszSeparator);
+ AssertRCBreak(rc);
+
+ cchString += cchSep;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppszBuffer = pszString;
+ *pcbBuffer = pszString ? cchString + 1 /* Include termination */ : 0;
+ }
+ else
+ RTStrFree(pszString);
+ return rc;
+}
+
+/**
+ * Returns all root entries for a DnD transfer list.
+ *
+ * Note: Convenience function which uses the default DnD path separator.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to return root entries for.
+ * @param enmFmt Which format to use for returning the entries.
+ * @param ppszBuffer Where to return the allocated string on success. Needs to be free'd with RTStrFree().
+ * @param pcbBuffer Where to return the size (in bytes) of the allocated string on success, including terminator.
+ */
+int DnDTransferListGetRoots(PDNDTRANSFERLIST pList,
+ DNDTRANSFERLISTFMT enmFmt, char **ppszBuffer, size_t *pcbBuffer)
+{
+ return DnDTransferListGetRootsEx(pList, enmFmt, "" /* pcszPathRoot */, DND_PATH_SEPARATOR_STR,
+ ppszBuffer, pcbBuffer);
+}
+
+/**
+ * Returns the total root entries count for a DnD transfer list.
+ *
+ * @returns Total number of root entries.
+ * @param pList Transfer list to return total number of root entries for.
+ */
+uint64_t DnDTransferListGetRootCount(PDNDTRANSFERLIST pList)
+{
+ AssertPtrReturn(pList, 0);
+ return pList->cRoots;
+}
+
+/**
+ * Returns the absolute root path for a DnD transfer list.
+ *
+ * @returns Pointer to the root path.
+ * @param pList Transfer list to return root path for.
+ */
+const char *DnDTransferListGetRootPathAbs(PDNDTRANSFERLIST pList)
+{
+ AssertPtrReturn(pList, NULL);
+ return pList->pszPathRootAbs;
+}
+
+/**
+ * Returns the total transfer object count for a DnD transfer list.
+ *
+ * @returns Total number of transfer objects.
+ * @param pList Transfer list to return total number of transfer objects for.
+ */
+uint64_t DnDTransferListObjCount(PDNDTRANSFERLIST pList)
+{
+ AssertPtrReturn(pList, 0);
+ return pList->cObj;
+}
+
+/**
+ * Returns the total bytes of all handled transfer objects for a DnD transfer list.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to return total bytes for.
+ */
+uint64_t DnDTransferListObjTotalBytes(PDNDTRANSFERLIST pList)
+{
+ AssertPtrReturn(pList, 0);
+ return pList->cbObjTotal;
+}
+
+/**
+ * Sets the absolute root path of a transfer list.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to set root path for.
+ * @param pcszRootPathAbs Absolute root path to set.
+ */
+static int dndTransferListSetRootPath(PDNDTRANSFERLIST pList, const char *pcszRootPathAbs)
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcszRootPathAbs, VERR_INVALID_POINTER);
+ AssertReturn(pList->pszPathRootAbs == NULL, VERR_WRONG_ORDER); /* Already initialized? */
+
+ LogFlowFunc(("pcszRootPathAbs=%s\n", pcszRootPathAbs));
+
+ char szRootPath[RTPATH_MAX];
+ int rc = RTStrCopy(szRootPath, sizeof(szRootPath), pcszRootPathAbs);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Note: The list's root path is kept in native style, so no conversion needed here. */
+
+ RTPathEnsureTrailingSeparatorEx(szRootPath, sizeof(szRootPath), RTPATH_STR_F_STYLE_HOST);
+
+ /* Make sure the root path is a directory (and no symlink or stuff). */
+ RTFSOBJINFO objInfo;
+ rc = RTPathQueryInfo(szRootPath, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode & RTFS_TYPE_MASK))
+ {
+ pList->pszPathRootAbs = RTStrDup(szRootPath);
+ if (pList->pszPathRootAbs)
+ {
+ LogFlowFunc(("Root path is '%s'\n", pList->pszPathRootAbs));
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_NOT_A_DIRECTORY;
+ }
+
+ return rc;
+}
+
+/**
+ * Adds a root entry to a DnD transfer list.
+ *
+ * @returns VBox status code.
+ * @param pList Transfer list to add root entry to.
+ * @param pcszRoot Root entry to add.
+ */
+static int dndTransferListRootEntryAdd(PDNDTRANSFERLIST pList, const char *pcszRoot)
+{
+ AssertPtrReturn(pList->pszPathRootAbs, VERR_WRONG_ORDER); /* The list's root path must be set first. */
+
+ int rc;
+
+ /** @todo Handle / reject double entries. */
+
+ /* Get the index pointing to the relative path in relation to set the root path. */
+ const size_t idxPathToAdd = strlen(pList->pszPathRootAbs);
+ AssertReturn(strlen(pcszRoot) > idxPathToAdd, VERR_INVALID_PARAMETER); /* Should never happen (tm). */
+
+ PDNDTRANSFERLISTROOT pRoot = (PDNDTRANSFERLISTROOT)RTMemAllocZ(sizeof(DNDTRANSFERLISTROOT));
+ if (pRoot)
+ {
+ const char *pcszRootIdx = &pcszRoot[idxPathToAdd];
+
+ LogFlowFunc(("pcszRoot=%s\n", pcszRootIdx));
+
+ pRoot->pszPathRoot = RTStrDup(pcszRootIdx);
+ if (pRoot->pszPathRoot)
+ {
+ RTListAppend(&pList->lstRoot, &pRoot->Node);
+ pList->cRoots++;
+
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pRoot);
+ pRoot = NULL;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/**
+ * Removes (and destroys) a DnD transfer root entry.
+ *
+ * @param pList Transfer list to free root for.
+ * @param pRootObj Transfer list root to free. The pointer will be invalid after calling.
+ */
+static void dndTransferListRootEntryFree(PDNDTRANSFERLIST pList, PDNDTRANSFERLISTROOT pRootObj)
+{
+ if (!pRootObj)
+ return;
+
+ RTStrFree(pRootObj->pszPathRoot);
+
+ RTListNodeRemove(&pRootObj->Node);
+ RTMemFree(pRootObj);
+
+ AssertReturnVoid(pList->cRoots);
+ pList->cRoots--;
+}
+
diff --git a/src/VBox/GuestHost/DragAndDrop/DnDTransferObject.cpp b/src/VBox/GuestHost/DragAndDrop/DnDTransferObject.cpp
new file mode 100644
index 00000000..6ae72fa8
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDTransferObject.cpp
@@ -0,0 +1,706 @@
+/* $Id: DnDTransferObject.cpp $ */
+/** @file
+ * DnD - Transfer object implemenation for handling creation/reading/writing to files and directories on host or guest side.
+ */
+
+/*
+ * Copyright (C) 2014-2022 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_GUEST_DND
+#include <VBox/GuestHost/DragAndDrop.h>
+
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/fs.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/uri.h>
+
+#include <VBox/log.h>
+
+
+/*********************************************************************************************************************************
+* Prototypes *
+*********************************************************************************************************************************/
+static int dndTransferObjectCloseInternal(PDNDTRANSFEROBJECT pObj);
+static int dndTransferObjectQueryInfoInternal(PDNDTRANSFEROBJECT pObj);
+
+
+/**
+ * Initializes the object, internal version.
+ *
+ * @returns VBox status code.
+ * @param pObj DnD transfer object to initialize.
+ */
+static int dndTransferObjectInitInternal(PDNDTRANSFEROBJECT pObj)
+{
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+
+ pObj->enmType = DNDTRANSFEROBJTYPE_UNKNOWN;
+ pObj->idxDst = 0;
+ pObj->pszPath = NULL;
+
+ RT_ZERO(pObj->u);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Initializes the object.
+ *
+ * @returns VBox status code.
+ * @param pObj DnD transfer object to initialize.
+ */
+int DnDTransferObjectInit(PDNDTRANSFEROBJECT pObj)
+{
+ return dndTransferObjectInitInternal(pObj);
+}
+
+/**
+ * Initializes the object with an expected object type and file path.
+ *
+ * @returns VBox status code.
+ * @param pObj DnD transfer object to initialize.
+ * @param enmType Type we expect this object to be.
+ * @param pcszPathSrcAbs Absolute source (local) path of file this object represents. Can be empty (e.g. for root stuff).
+ * @param pcszPathDst Relative path of file this object represents at the destination.
+ * Together with \a pcszPathSrcAbs this represents the complete absolute local path.
+ */
+int DnDTransferObjectInitEx(PDNDTRANSFEROBJECT pObj,
+ DNDTRANSFEROBJTYPE enmType, const char *pcszPathSrcAbs, const char *pcszPathDst)
+{
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+ AssertReturn(pObj->enmType == DNDTRANSFEROBJTYPE_UNKNOWN, VERR_WRONG_ORDER); /* Already initialized? */
+ /* pcszPathSrcAbs can be empty. */
+ AssertPtrReturn(pcszPathDst, VERR_INVALID_POINTER);
+
+ int rc = dndTransferObjectInitInternal(pObj);
+ AssertRCReturn(rc, rc);
+
+ rc = DnDPathValidate(pcszPathDst, false /* Does not need to exist */);
+ AssertRCReturn(rc, rc);
+
+ char szPath[RTPATH_MAX + 1];
+
+ /* Save the index (in characters) where the first destination segment starts. */
+ if ( pcszPathSrcAbs
+ && RTStrNLen(pcszPathSrcAbs, RTSTR_MAX))
+ {
+ rc = DnDPathValidate(pcszPathSrcAbs, false /* Does not need to exist */);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = RTStrCopy(szPath, sizeof(szPath), pcszPathSrcAbs);
+ if (RT_SUCCESS(rc))
+ rc = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath)) == 0 ? VERR_BUFFER_OVERFLOW : VINF_SUCCESS;
+
+ /* Save the index (in characters) where the destination part starts. */
+ pObj->idxDst = (uint16_t)RTStrNLen(szPath, RTPATH_MAX);
+ AssertReturn(pObj->idxDst <= RTPATH_MAX, VERR_INVALID_PARAMETER);
+ }
+ else
+ {
+ szPath[0] = '\0'; /* Init empty string. */
+ pObj->idxDst = 0;
+ }
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Append the destination part. */
+ rc = RTPathAppend(szPath, sizeof(szPath), pcszPathDst);
+ if ( RT_SUCCESS(rc)
+ && enmType == DNDTRANSFEROBJTYPE_DIRECTORY)
+ rc = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath)) == 0 ? VERR_BUFFER_OVERFLOW : VINF_SUCCESS;
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pObj->pszPath = RTStrDup(szPath);
+ if (!pObj->pszPath)
+ return VERR_NO_MEMORY;
+
+ /* Convert paths into transport format. */
+ rc = DnDPathConvert(pObj->pszPath, strlen(pObj->pszPath), DNDPATHCONVERT_FLAGS_TRANSPORT);
+ if (RT_FAILURE(rc))
+ {
+ RTStrFree(pObj->pszPath);
+ pObj->pszPath = NULL;
+ return rc;
+ }
+
+ LogFlowFunc(("enmType=%RU32, pcszPathSrcAbs=%s, pcszPathDst=%s -> pszPath=%s\n",
+ enmType, pcszPathSrcAbs, pcszPathDst, pObj->pszPath));
+
+ pObj->enmType = enmType;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a DnD transfer object.
+ *
+ * @param pObj DnD transfer object to destroy.
+ */
+void DnDTransferObjectDestroy(PDNDTRANSFEROBJECT pObj)
+{
+ if (!pObj)
+ return;
+
+ DnDTransferObjectReset(pObj);
+}
+
+/**
+ * Closes the object's internal handles (to files / ...).
+ *
+ * @returns VBox status code.
+ * @param pObj DnD transfer object to close internally.
+ */
+static int dndTransferObjectCloseInternal(PDNDTRANSFEROBJECT pObj)
+{
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ if (pObj->pszPath)
+ LogRel2(("DnD: Closing '%s'\n", pObj->pszPath));
+
+ switch (pObj->enmType)
+ {
+ case DNDTRANSFEROBJTYPE_FILE:
+ {
+ if (RTFileIsValid(pObj->u.File.hFile))
+ {
+ rc = RTFileClose(pObj->u.File.hFile);
+ if (RT_SUCCESS(rc))
+ {
+ pObj->u.File.hFile = NIL_RTFILE;
+ RT_ZERO(pObj->u.File.objInfo);
+ }
+ else
+ LogRel(("DnD: Closing file '%s' failed with %Rrc\n", pObj->pszPath, rc));
+ }
+ break;
+ }
+
+ case DNDTRANSFEROBJTYPE_DIRECTORY:
+ {
+ if (RTDirIsValid(pObj->u.Dir.hDir))
+ {
+ rc = RTDirClose(pObj->u.Dir.hDir);
+ if (RT_SUCCESS(rc))
+ {
+ pObj->u.Dir.hDir = NIL_RTDIR;
+ RT_ZERO(pObj->u.Dir.objInfo);
+ }
+ else
+ LogRel(("DnD: Closing directory '%s' failed with %Rrc\n", pObj->pszPath, rc));
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+/**
+ * Closes the object.
+ * This also closes the internal handles associated with the object (to files / ...).
+ *
+ * @returns VBox status code.
+ * @param pObj DnD transfer object to close.
+ */
+int DnDTransferObjectClose(PDNDTRANSFEROBJECT pObj)
+{
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+
+ return dndTransferObjectCloseInternal(pObj);
+}
+
+/**
+ * Returns the absolute source path of the object.
+ *
+ * @return Absolute source path of the object.
+ * @param pObj DnD transfer object to get source path for.
+ */
+const char *DnDTransferObjectGetSourcePath(PDNDTRANSFEROBJECT pObj)
+{
+ AssertPtrReturn(pObj, NULL);
+ return pObj->pszPath;
+}
+
+/**
+ * Returns the (relative) destination path of the object, in transport style.
+ *
+ * @return Relative destination path of the object, or NULL if not set.
+ * @param pObj DnD transfer object to get destination path for.
+ */
+const char *DnDTransferObjectGetDestPath(PDNDTRANSFEROBJECT pObj)
+{
+ AssertPtrReturn(pObj, NULL);
+
+ if (!pObj->pszPath)
+ return NULL;
+
+ AssertReturn(strlen(pObj->pszPath) >= pObj->idxDst, NULL);
+
+ return &pObj->pszPath[pObj->idxDst];
+}
+
+/**
+ * Returns the (relative) destination path of the object, extended version.
+ *
+ * @return VBox status code, or VERR_NOT_FOUND if not initialized yet.
+ * @param pObj DnD transfer object to get destination path for.
+ * @param enmStyle Which path style to return.
+ * @param pszBuf Where to store the path.
+ * @param cbBuf Size (in bytes) where to store the path.
+ */
+int DnDTransferObjectGetDestPathEx(PDNDTRANSFEROBJECT pObj, DNDTRANSFEROBJPATHSTYLE enmStyle, char *pszBuf, size_t cbBuf)
+{
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+
+ if (!pObj->pszPath)
+ return VERR_NOT_FOUND;
+
+ AssertReturn(strlen(pObj->pszPath) >= pObj->idxDst, VERR_INTERNAL_ERROR);
+
+ int rc = RTStrCopy(pszBuf, cbBuf, &pObj->pszPath[pObj->idxDst]);
+ if ( RT_SUCCESS(rc)
+ && enmStyle == DNDTRANSFEROBJPATHSTYLE_DOS)
+ rc = DnDPathConvert(pszBuf, cbBuf, DNDPATHCONVERT_FLAGS_TO_DOS);
+
+ return rc;
+}
+
+/**
+ * Returns the directory / file mode of the object.
+ *
+ * @return File / directory mode.
+ * @param pObj DnD transfer object to get directory / file mode for.
+ */
+RTFMODE DnDTransferObjectGetMode(PDNDTRANSFEROBJECT pObj)
+{
+ AssertPtrReturn(pObj, 0);
+
+ switch (pObj->enmType)
+ {
+ case DNDTRANSFEROBJTYPE_FILE:
+ return pObj->u.File.objInfo.Attr.fMode;
+
+ case DNDTRANSFEROBJTYPE_DIRECTORY:
+ return pObj->u.Dir.objInfo.Attr.fMode;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * Returns the bytes already processed (read / written).
+ *
+ * Note: Only applies if the object is of type DnDTransferObjectType_File.
+ *
+ * @return Bytes already processed (read / written).
+ * @param pObj DnD transfer object to get processed bytes for.
+ */
+uint64_t DnDTransferObjectGetProcessed(PDNDTRANSFEROBJECT pObj)
+{
+ if (pObj->enmType == DNDTRANSFEROBJTYPE_FILE)
+ return pObj->u.File.cbProcessed;
+
+ return 0;
+}
+
+/**
+ * Returns the file's logical size (in bytes).
+ *
+ * Note: Only applies if the object is of type DnDTransferObjectType_File.
+ *
+ * @return The file's logical size (in bytes).
+ * @param pObj DnD transfer object to get size for.
+ */
+uint64_t DnDTransferObjectGetSize(PDNDTRANSFEROBJECT pObj)
+{
+ if (pObj->enmType == DNDTRANSFEROBJTYPE_FILE)
+ return pObj->u.File.cbToProcess;
+
+ return 0;
+}
+
+/**
+ * Returns the object's type.
+ *
+ * @return The object's type.
+ * @param pObj DnD transfer object to get type for.
+ */
+DNDTRANSFEROBJTYPE DnDTransferObjectGetType(PDNDTRANSFEROBJECT pObj)
+{
+ return pObj->enmType;
+}
+
+/**
+ * Returns whether the processing of the object is complete or not.
+ * For file objects this means that all bytes have been processed.
+ *
+ * @return True if complete, False if not.
+ * @param pObj DnD transfer object to get completion status for.
+ */
+bool DnDTransferObjectIsComplete(PDNDTRANSFEROBJECT pObj)
+{
+ bool fComplete;
+
+ switch (pObj->enmType)
+ {
+ case DNDTRANSFEROBJTYPE_FILE:
+ Assert(pObj->u.File.cbProcessed <= pObj->u.File.cbToProcess);
+ fComplete = pObj->u.File.cbProcessed == pObj->u.File.cbToProcess;
+ break;
+
+ case DNDTRANSFEROBJTYPE_DIRECTORY:
+ fComplete = true;
+ break;
+
+ default:
+ fComplete = true;
+ break;
+ }
+
+ return fComplete;
+}
+
+/**
+ * Returns whether the object is in an open state or not.
+ * @param pObj DnD transfer object to get open status for.
+ */
+bool DnDTransferObjectIsOpen(PDNDTRANSFEROBJECT pObj)
+{
+ switch (pObj->enmType)
+ {
+ case DNDTRANSFEROBJTYPE_FILE: return RTFileIsValid(pObj->u.File.hFile);
+ case DNDTRANSFEROBJTYPE_DIRECTORY: return RTDirIsValid(pObj->u.Dir.hDir);
+ default: break;
+ }
+
+ return false;
+}
+
+/**
+ * Open the object with a specific file type, and, depending on the type, specifying additional parameters.
+ *
+ * @return IPRT status code.
+ * @param pObj DnD transfer object to open.
+ * @param fOpen Open mode to use; only valid for file objects.
+ * @param fMode File mode to set; only valid for file objects. Depends on fOpen and and can be 0.
+ * @param fFlags Additional DnD transfer object flags.
+ */
+int DnDTransferObjectOpen(PDNDTRANSFEROBJECT pObj, uint64_t fOpen, RTFMODE fMode, DNDTRANSFEROBJECTFLAGS fFlags)
+{
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+ AssertReturn(fOpen, VERR_INVALID_FLAGS);
+ /* fMode is optional. */
+ AssertReturn(!(fFlags & ~DNDTRANSFEROBJECT_FLAGS_VALID_MASK), VERR_INVALID_FLAGS);
+ RT_NOREF1(fFlags);
+
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pszPath=%s, fOpen=0x%x, fMode=0x%x, fFlags=0x%x\n", pObj->pszPath, fOpen, fMode, fFlags));
+
+ switch (pObj->enmType)
+ {
+ case DNDTRANSFEROBJTYPE_FILE:
+ {
+ LogRel2(("DnD: Opening file '%s'\n", pObj->pszPath));
+
+ /*
+ * Open files on the source with RTFILE_O_DENY_WRITE to prevent races
+ * where the OS writes to the file while the destination side transfers
+ * it over.
+ */
+ rc = RTFileOpen(&pObj->u.File.hFile, pObj->pszPath, fOpen);
+ if (RT_SUCCESS(rc))
+ {
+ if ( (fOpen & RTFILE_O_WRITE) /* Only set the file mode on write. */
+ && fMode /* Some file mode to set specified? */)
+ {
+ rc = RTFileSetMode(pObj->u.File.hFile, fMode);
+ if (RT_FAILURE(rc))
+ LogRel(("DnD: Setting mode %#x for file '%s' failed with %Rrc\n", fMode, pObj->pszPath, rc));
+ }
+ else if (fOpen & RTFILE_O_READ)
+ {
+ rc = dndTransferObjectQueryInfoInternal(pObj);
+ }
+ }
+ else
+ LogRel(("DnD: Opening file '%s' failed with %Rrc\n", pObj->pszPath, rc));
+
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("File cbObject=%RU64, fMode=0x%x\n",
+ pObj->u.File.objInfo.cbObject, pObj->u.File.objInfo.Attr.fMode));
+ pObj->u.File.cbToProcess = pObj->u.File.objInfo.cbObject;
+ pObj->u.File.cbProcessed = 0;
+ }
+
+ break;
+ }
+
+ case DNDTRANSFEROBJTYPE_DIRECTORY:
+ {
+ LogRel2(("DnD: Opening directory '%s'\n", pObj->pszPath));
+
+ rc = RTDirOpen(&pObj->u.Dir.hDir, pObj->pszPath);
+ if (RT_SUCCESS(rc))
+ {
+ rc = dndTransferObjectQueryInfoInternal(pObj);
+ }
+ else
+ LogRel(("DnD: Opening directory '%s' failed with %Rrc\n", pObj->pszPath, rc));
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Queries information about the object using a specific view, internal version.
+ *
+ * @return IPRT status code.
+ * @param pObj DnD transfer object to query info for.
+ */
+static int dndTransferObjectQueryInfoInternal(PDNDTRANSFEROBJECT pObj)
+{
+ int rc;
+
+ switch (pObj->enmType)
+ {
+ case DNDTRANSFEROBJTYPE_FILE:
+ AssertMsgReturn(RTFileIsValid(pObj->u.File.hFile), ("Object has invalid file handle\n"), VERR_INVALID_STATE);
+ rc = RTFileQueryInfo(pObj->u.File.hFile, &pObj->u.File.objInfo, RTFSOBJATTRADD_NOTHING);
+ break;
+
+ case DNDTRANSFEROBJTYPE_DIRECTORY:
+ AssertMsgReturn(RTDirIsValid(pObj->u.Dir.hDir), ("Object has invalid directory handle\n"), VERR_INVALID_STATE);
+ rc = RTDirQueryInfo(pObj->u.Dir.hDir, &pObj->u.Dir.objInfo, RTFSOBJATTRADD_NOTHING);
+ break;
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("DnD: Querying information for '%s' failed with %Rrc\n", pObj->pszPath, rc));
+
+ return rc;
+}
+
+/**
+ * Queries information about the object using a specific view.
+ *
+ * @return IPRT status code.
+ * @param pObj DnD transfer object to query info for.
+ */
+int DnDTransferObjectQueryInfo(PDNDTRANSFEROBJECT pObj)
+{
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+ return dndTransferObjectQueryInfoInternal(pObj);
+}
+
+/**
+ * Reads data from the object. Only applies to files objects.
+ *
+ * @return IPRT status code.
+ * @param pObj DnD transfer object to read data from.
+ * @param pvBuf Buffer where to store the read data.
+ * @param cbBuf Size (in bytes) of the buffer.
+ * @param pcbRead Pointer where to store how many bytes were read. Optional.
+ */
+int DnDTransferObjectRead(PDNDTRANSFEROBJECT pObj, void *pvBuf, size_t cbBuf, uint32_t *pcbRead)
+{
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ /* pcbRead is optional. */
+
+ size_t cbRead = 0;
+
+ int rc;
+ switch (pObj->enmType)
+ {
+ case DNDTRANSFEROBJTYPE_FILE:
+ {
+ rc = RTFileRead(pObj->u.File.hFile, pvBuf, cbBuf, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ pObj->u.File.cbProcessed += cbRead;
+ Assert(pObj->u.File.cbProcessed <= pObj->u.File.cbToProcess);
+
+ /* End of file reached or error occurred? */
+ if ( pObj->u.File.cbToProcess
+ && pObj->u.File.cbProcessed == pObj->u.File.cbToProcess)
+ {
+ rc = VINF_EOF;
+ }
+ }
+ else
+ LogRel(("DnD: Reading from file '%s' failed with %Rrc\n", pObj->pszPath, rc));
+ break;
+ }
+
+ case DNDTRANSFEROBJTYPE_DIRECTORY:
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcbRead)
+ *pcbRead = (uint32_t)cbRead;
+ }
+
+ LogFlowFunc(("Returning cbRead=%zu, rc=%Rrc\n", cbRead, rc));
+ return rc;
+}
+
+/**
+ * Resets the object's state and closes all related handles.
+ *
+ * @param pObj DnD transfer object to reset.
+ */
+void DnDTransferObjectReset(PDNDTRANSFEROBJECT pObj)
+{
+ AssertPtrReturnVoid(pObj);
+
+ LogFlowFuncEnter();
+
+ int vrc2 = dndTransferObjectCloseInternal(pObj);
+ AssertRCReturnVoid(vrc2);
+
+ pObj->enmType = DNDTRANSFEROBJTYPE_UNKNOWN;
+ pObj->idxDst = 0;
+
+ RTStrFree(pObj->pszPath);
+ pObj->pszPath = NULL;
+
+ RT_ZERO(pObj->u);
+}
+
+/**
+ * Sets the bytes to process by the object.
+ *
+ * Note: Only applies if the object is of type DnDTransferObjectType_File.
+ *
+ * @return IPRT return code.
+ * @param pObj DnD transfer object to set size for.
+ * @param cbSize Size (in bytes) to process.
+ */
+int DnDTransferObjectSetSize(PDNDTRANSFEROBJECT pObj, uint64_t cbSize)
+{
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+ AssertReturn(pObj->enmType == DNDTRANSFEROBJTYPE_FILE, VERR_INVALID_PARAMETER);
+
+ /** @todo Implement sparse file support here. */
+
+ pObj->u.File.cbToProcess = cbSize;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes data to an object. Only applies to file objects.
+ *
+ * @return IPRT status code.
+ * @param pObj DnD transfer object to write to.
+ * @param pvBuf Buffer of data to write.
+ * @param cbBuf Size (in bytes) of data to write.
+ * @param pcbWritten Pointer where to store how many bytes were written. Optional.
+ */
+int DnDTransferObjectWrite(PDNDTRANSFEROBJECT pObj, const void *pvBuf, size_t cbBuf, uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ /* pcbWritten is optional. */
+
+ size_t cbWritten = 0;
+
+ int rc;
+ switch (pObj->enmType)
+ {
+ case DNDTRANSFEROBJTYPE_FILE:
+ {
+ rc = RTFileWrite(pObj->u.File.hFile, pvBuf, cbBuf, &cbWritten);
+ if (RT_SUCCESS(rc))
+ {
+ pObj->u.File.cbProcessed += cbWritten;
+ }
+ else
+ LogRel(("DnD: Writing to file '%s' failed with %Rrc\n", pObj->pszPath, rc));
+ break;
+ }
+
+ case DNDTRANSFEROBJTYPE_DIRECTORY:
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcbWritten)
+ *pcbWritten = (uint32_t)cbWritten;
+ }
+
+ LogFlowFunc(("Returning cbWritten=%zu, rc=%Rrc\n", cbWritten, rc));
+ return rc;
+}
+
diff --git a/src/VBox/GuestHost/DragAndDrop/DnDUtils.cpp b/src/VBox/GuestHost/DragAndDrop/DnDUtils.cpp
new file mode 100644
index 00000000..d2a2067d
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDUtils.cpp
@@ -0,0 +1,177 @@
+/* $Id: DnDUtils.cpp $ */
+/** @file
+ * DnD - Common utility functions.
+ */
+
+/*
+ * Copyright (C) 2022 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 *
+*********************************************************************************************************************************/
+#include <VBox/GuestHost/DragAndDrop.h>
+#include <VBox/HostServices/DragAndDropSvc.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+using namespace DragAndDropSvc;
+
+/**
+ * Converts a host HGCM message to a string.
+ *
+ * @returns Stringified version of the host message.
+ */
+const char *DnDHostMsgToStr(uint32_t uMsg)
+{
+ switch (uMsg)
+ {
+ RT_CASE_RET_STR(HOST_DND_FN_SET_MODE);
+ RT_CASE_RET_STR(HOST_DND_FN_CANCEL);
+ RT_CASE_RET_STR(HOST_DND_FN_HG_EVT_ENTER);
+ RT_CASE_RET_STR(HOST_DND_FN_HG_EVT_MOVE);
+ RT_CASE_RET_STR(HOST_DND_FN_HG_EVT_LEAVE);
+ RT_CASE_RET_STR(HOST_DND_FN_HG_EVT_DROPPED);
+ RT_CASE_RET_STR(HOST_DND_FN_HG_SND_DATA_HDR);
+ RT_CASE_RET_STR(HOST_DND_FN_HG_SND_DATA);
+ RT_CASE_RET_STR(HOST_DND_FN_HG_SND_MORE_DATA);
+ RT_CASE_RET_STR(HOST_DND_FN_HG_SND_DIR);
+ RT_CASE_RET_STR(HOST_DND_FN_HG_SND_FILE_DATA);
+ RT_CASE_RET_STR(HOST_DND_FN_HG_SND_FILE_HDR);
+ RT_CASE_RET_STR(HOST_DND_FN_GH_REQ_PENDING);
+ RT_CASE_RET_STR(HOST_DND_FN_GH_EVT_DROPPED);
+ default:
+ break;
+ }
+ return "unknown";
+}
+
+/**
+ * Converts a guest HGCM message to a string.
+ *
+ * @returns Stringified version of the guest message.
+ */
+const char *DnDGuestMsgToStr(uint32_t uMsg)
+{
+ switch (uMsg)
+ {
+ RT_CASE_RET_STR(GUEST_DND_FN_CONNECT);
+ RT_CASE_RET_STR(GUEST_DND_FN_DISCONNECT);
+ RT_CASE_RET_STR(GUEST_DND_FN_REPORT_FEATURES);
+ RT_CASE_RET_STR(GUEST_DND_FN_QUERY_FEATURES);
+ RT_CASE_RET_STR(GUEST_DND_FN_GET_NEXT_HOST_MSG);
+ RT_CASE_RET_STR(GUEST_DND_FN_EVT_ERROR);
+ RT_CASE_RET_STR(GUEST_DND_FN_HG_ACK_OP);
+ RT_CASE_RET_STR(GUEST_DND_FN_HG_REQ_DATA);
+ RT_CASE_RET_STR(GUEST_DND_FN_HG_EVT_PROGRESS);
+ RT_CASE_RET_STR(GUEST_DND_FN_GH_ACK_PENDING);
+ RT_CASE_RET_STR(GUEST_DND_FN_GH_SND_DATA_HDR);
+ RT_CASE_RET_STR(GUEST_DND_FN_GH_SND_DATA);
+ RT_CASE_RET_STR(GUEST_DND_FN_GH_SND_DIR);
+ RT_CASE_RET_STR(GUEST_DND_FN_GH_SND_FILE_DATA);
+ RT_CASE_RET_STR(GUEST_DND_FN_GH_SND_FILE_HDR);
+ default:
+ break;
+ }
+ return "unknown";
+}
+
+/**
+ * Converts a VBOXDNDACTION to a string.
+ *
+ * @returns Stringified version of VBOXDNDACTION
+ * @param uAction DnD action to convert.
+ */
+const char *DnDActionToStr(VBOXDNDACTION uAction)
+{
+ switch (uAction)
+ {
+ case VBOX_DND_ACTION_IGNORE: return "ignore";
+ case VBOX_DND_ACTION_COPY: return "copy";
+ case VBOX_DND_ACTION_MOVE: return "move";
+ case VBOX_DND_ACTION_LINK: return "link";
+ default:
+ break;
+ }
+ AssertMsgFailedReturn(("Unknown uAction=%d\n", uAction), "bad");
+}
+
+/**
+ * Converts a VBOXDNDACTIONLIST to a string.
+ *
+ * @returns Stringified version of VBOXDNDACTIONLIST. Must be free'd by the caller using RTStrFree().
+ * @retval NULL on allocation failure.
+ * @retval "<None>" if no (valid) actions found.
+ * @param fActionList DnD action list to convert.
+ */
+char *DnDActionListToStrA(VBOXDNDACTIONLIST fActionList)
+{
+ char *pszList = NULL;
+
+#define HANDLE_ACTION(a_Action) \
+ if (fActionList & a_Action) \
+ { \
+ if (pszList) \
+ AssertRCReturn(RTStrAAppend(&pszList, ", "), NULL); \
+ AssertRCReturn(RTStrAAppend(&pszList, DnDActionToStr(a_Action)), NULL); \
+ }
+
+ HANDLE_ACTION(VBOX_DND_ACTION_IGNORE);
+ HANDLE_ACTION(VBOX_DND_ACTION_COPY);
+ HANDLE_ACTION(VBOX_DND_ACTION_MOVE);
+ HANDLE_ACTION(VBOX_DND_ACTION_LINK);
+
+#undef HANDLE_ACTION
+
+ if (!pszList)
+ AssertRCReturn(RTStrAAppend(&pszList, "<None>"), NULL);
+
+ return pszList;
+}
+
+/**
+ * Converts a VBOXDNDSTATE to a string.
+ *
+ * @returns Stringified version of VBOXDNDSTATE.
+ * @param enmState DnD state to convert.
+ */
+const char *DnDStateToStr(VBOXDNDSTATE enmState)
+{
+ switch (enmState)
+ {
+ case VBOXDNDSTATE_UNKNOWN: return "unknown";
+ case VBOXDNDSTATE_ENTERED: return "entered VM window";
+ case VBOXDNDSTATE_LEFT: return "left VM window";
+ case VBOXDNDSTATE_QUERY_FORMATS: return "querying formats";
+ case VBOXDNDSTATE_QUERY_STATUS: return "querying status";
+ case VBOXDNDSTATE_DRAGGING: return "dragging";
+ case VBOXDNDSTATE_DROP_STARTED: return "drop started";
+ case VBOXDNDSTATE_DROP_ENDED: return "drop ended";
+ case VBOXDNDSTATE_CANCELLED: return "cancelled";
+ case VBOXDNDSTATE_ERROR: return "error";
+ default:
+ break;
+ }
+ AssertMsgFailedReturn(("Unknown enmState=%d\n", enmState), "bad");
+}
+
diff --git a/src/VBox/GuestHost/DragAndDrop/Makefile.kmk b/src/VBox/GuestHost/DragAndDrop/Makefile.kmk
new file mode 100644
index 00000000..a04281b8
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/Makefile.kmk
@@ -0,0 +1,73 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the shared DnD code for both, host and guest.
+#
+
+#
+# Copyright (C) 2014-2022 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
+
+ifdef VBOX_WITH_TESTCASES
+ include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk
+endif
+
+VBOX_DND_GUESTHOST_FILES := \
+ DnDDroppedFiles.cpp \
+ DnDMIME.cpp \
+ DnDPath.cpp \
+ DnDTransferObject.cpp \
+ DnDTransferList.cpp \
+ DnDUtils.cpp
+
+#
+# DnDGuestR3Lib - For tools on the guest side,
+# e.g. VBoxClient/VBoxTray.
+#
+ifdef VBOX_WITH_ADDITIONS
+ LIBRARIES += VBoxDnDGuestR3Lib
+ VBoxDnDGuestR3Lib_TEMPLATE = VBOXGUESTR3LIB
+ VBoxDnDGuestR3Lib_DEFS =
+ VBoxDnDGuestR3Lib_SOURCES = $(VBOX_DND_GUESTHOST_FILES)
+
+# LIBRARIES.win.amd64 += VBoxDnDGuestR3Lib-x86
+# VBoxDnDGuestR3Lib-x86_EXTENDS := VBoxDnDGuestR3Lib
+# VBoxDnDGuestR3Lib-x86_BLD_TRG_ARCH := x86
+endif
+
+#
+# DnDHostR3Lib - For the host side, e.g. Main
+# and frontends.
+#
+LIBRARIES += VBoxDnDHostR3Lib
+VBoxDnDHostR3Lib_TEMPLATE = VBOXR3
+VBoxDnDHostR3Lib_DEFS =
+VBoxDnDHostR3Lib_SOURCES = $(VBOX_DND_GUESTHOST_FILES)
+
+# Include the common host services code.
+VBOX_PATH_HOSTSERVICES_COMMON = $(PATH_ROOT)/src/VBox/HostServices/common
+VBoxDnDHostR3Lib_SOURCES += \
+ $(VBOX_PATH_HOSTSERVICES_COMMON)/client.cpp \
+ $(VBOX_PATH_HOSTSERVICES_COMMON)/message.cpp
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/GuestHost/DragAndDrop/testcase/Makefile.kmk b/src/VBox/GuestHost/DragAndDrop/testcase/Makefile.kmk
new file mode 100644
index 00000000..537009e3
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/testcase/Makefile.kmk
@@ -0,0 +1,81 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the DnD testcases.
+#
+
+#
+# Copyright (C) 2020-2022 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)
+
+ if defined(VBOX_WITH_QTGUI)
+ include $(PATH_SUB_CURRENT)/tstDragAndDropQt/Makefile.kmk
+ endif
+
+ PROGRAMS += tstDnDTransferObject tstDnDTransferList tstDnDPath
+ TESTING += \
+ $(tstDnDTransferObject_0_OUTDIR)/tstDnDTransferObject.run \
+ $(tstDnDTransferList_0_OUTDIR)/tstDnDTransferList.run \
+ $(tstDnDTransferPath_0_OUTDIR)/tstDnDPath.run
+
+ tstDnDTransferObject_TEMPLATE = VBOXR3TSTEXE
+ tstDnDTransferObject_DEFS = UNIT_TEST TESTCASE
+ tstDnDTransferObject_SOURCES = \
+ tstDnDTransferObject.cpp \
+ ../DnDTransferObject.cpp \
+ ../DnDPath.cpp
+ tstDnDTransferObject_CLEAN = $(tstDnDTransferObject_0_OUTDIR)/tstDnDTransferObject.run
+
+ $$(tstDnDTransferObject_0_OUTDIR)/tstDnDTransferObject.run: $$(tstDnDTransferObject_1_STAGE_TARGET)
+ export VBOX_LOG_DEST=nofile; $(tstDnDTransferObject_1_STAGE_TARGET) quiet
+ $(QUIET)$(APPEND) -t "$@" "done"
+
+ tstDnDTransferList_TEMPLATE = VBOXR3TSTEXE
+ tstDnDTransferList_DEFS = UNIT_TEST TESTCASE
+ tstDnDTransferList_SOURCES = \
+ tstDnDTransferList.cpp \
+ ../DnDTransferObject.cpp \
+ ../DnDTransferList.cpp \
+ ../DnDPath.cpp
+ tstDnDTransferList_CLEAN = $(tstDnDTransferList_0_OUTDIR)/tstDnDTransferList.run
+
+ $$(tstDnDTransferList_0_OUTDIR)/tstDnDTransferList.run: $$(tstDnDTransferList_1_STAGE_TARGET)
+ export VBOX_LOG_DEST=nofile; $(tstDnDTransferList_1_STAGE_TARGET) quiet
+ $(QUIET)$(APPEND) -t "$@" "done"
+
+ tstDnDPath_TEMPLATE = VBOXR3TSTEXE
+ tstDnDPath_DEFS = UNIT_TEST TESTCASE
+ tstDnDPath_SOURCES = \
+ tstDnDPath.cpp \
+ ../DnDPath.cpp
+ tstDnDPath_CLEAN = $(tstDnDPath_0_OUTDIR)/tstDnDPath.run
+
+ $$(tstDnDPath_0_OUTDIR)/tstDnDPath.run: $$(tstDnDPath_1_STAGE_TARGET)
+ export VBOX_LOG_DEST=nofile; $(tstDnDPath_1_STAGE_TARGET) quiet
+ $(QUIET)$(APPEND) -t "$@" "done"
+
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/GuestHost/DragAndDrop/testcase/tstDnDPath.cpp b/src/VBox/GuestHost/DragAndDrop/testcase/tstDnDPath.cpp
new file mode 100644
index 00000000..5fb2ffc1
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/testcase/tstDnDPath.cpp
@@ -0,0 +1,100 @@
+/* $Id: tstDnDPath.cpp $ */
+/** @file
+ * DnD path tests.
+ */
+
+/*
+ * Copyright (C) 2020-2022 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 <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/errcore.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+
+#include <VBox/GuestHost/DragAndDrop.h>
+
+
+static void tstPathRebase(RTTEST hTest)
+{
+ static struct
+ {
+ char const *pszPath;
+ char const *pszPathOld;
+ char const *pszPathNew;
+ int rc;
+ char const *pszResult;
+ } const s_aTests[] = {
+ /* Invalid stuff. */
+ { NULL, NULL, NULL, VERR_INVALID_POINTER, NULL },
+ { "foo", "old", NULL, VERR_INVALID_POINTER, NULL },
+ /* Actual rebasing. */
+ { "old/foo", "old", "new", VINF_SUCCESS, "new/foo" },
+ /* Note: DnDPathRebase intentionally does not do any path conversions. */
+#ifdef RT_OS_WINDOWS
+ { "old\\foo", "old", "new", VINF_SUCCESS, "new/foo" },
+ { "\\totally\\different\\path\\foo", "/totally/different/path", "/totally/different/path", VINF_SUCCESS, "/totally/different/path/foo" },
+ { "\\old\\path\\foo", "", "/new/root/", VINF_SUCCESS, "/new/root/old/path/foo" },
+ { "\\\\old\\path\\\\foo", "", "/new/root/", VINF_SUCCESS, "/new/root/old/path\\\\foo" }
+#else
+ { "old/foo", "old", "new", VINF_SUCCESS, "new/foo" },
+ { "/totally/different/path/foo", "/totally/different/path", "/totally/different/path", VINF_SUCCESS, "/totally/different/path/foo" },
+ { "/old/path/foo", "", "/new/root/", VINF_SUCCESS, "/new/root/old/path/foo" },
+ { "//old/path//foo", "", "/new/root/", VINF_SUCCESS, "/new/root/old/path//foo" }
+#endif
+ };
+
+ char *pszPath = NULL;
+ for (size_t i = 0; i < RT_ELEMENTS(s_aTests); i++)
+ {
+ RTTestDisableAssertions(hTest);
+ RTTEST_CHECK_RC(hTest, DnDPathRebase(s_aTests[i].pszPath, s_aTests[i].pszPathOld, s_aTests[i].pszPathNew, &pszPath),
+ s_aTests[i].rc);
+ RTTestRestoreAssertions(hTest);
+ if (RT_SUCCESS(s_aTests[i].rc))
+ {
+ if (s_aTests[i].pszResult)
+ RTTEST_CHECK_MSG(hTest, RTPathCompare(pszPath, s_aTests[i].pszResult) == 0,
+ (hTest, "Test #%zu failed: Got '%s', expected '%s'", i, pszPath, s_aTests[i].pszResult));
+ RTStrFree(pszPath);
+ pszPath = NULL;
+ }
+ }
+}
+
+int main()
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstDnDPath", &hTest);
+ if (rc)
+ return rc;
+ RTTestBanner(hTest);
+
+ tstPathRebase(hTest);
+
+ return RTTestSummaryAndDestroy(hTest);
+}
+
diff --git a/src/VBox/GuestHost/DragAndDrop/testcase/tstDnDTransferList.cpp b/src/VBox/GuestHost/DragAndDrop/testcase/tstDnDTransferList.cpp
new file mode 100644
index 00000000..c5ef61f7
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/testcase/tstDnDTransferList.cpp
@@ -0,0 +1,191 @@
+/* $Id: tstDnDTransferList.cpp $ */
+/** @file
+ * DnD transfer list tests.
+ */
+
+/*
+ * Copyright (C) 2020-2022 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 <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+
+#include <VBox/GuestHost/DragAndDrop.h>
+
+
+int main()
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstDnDTransferList", &hTest);
+ if (rc)
+ return rc;
+ RTTestBanner(hTest);
+
+ char szPathWellKnown[RTPATH_MAX];
+ RTStrCopy(szPathWellKnown, sizeof(szPathWellKnown),
+#ifdef RT_OS_WINDOWS
+ "C:\\Windows\\System32\\Boot\\");
+#else
+ "/bin/");
+#endif
+
+ char szPathWellKnownURI[RTPATH_MAX];
+ RTStrPrintf(szPathWellKnownURI, sizeof(szPathWellKnownURI), "file:///%s", szPathWellKnown);
+
+ DNDTRANSFERLIST list;
+ RT_ZERO(list);
+
+ /* Invalid stuff. */
+ RTTestDisableAssertions(hTest);
+ RTTEST_CHECK_RC(hTest, DnDTransferListInitEx(&list, "", DNDTRANSFERLISTFMT_NATIVE), VERR_INVALID_PARAMETER);
+ RTTEST_CHECK_RC(hTest, DnDTransferListInitEx(&list, szPathWellKnown, DNDTRANSFERLISTFMT_NATIVE), VINF_SUCCESS);
+ RTTEST_CHECK_RC(hTest, DnDTransferListInitEx(&list, szPathWellKnown, DNDTRANSFERLISTFMT_NATIVE), VERR_WRONG_ORDER);
+ RTTestRestoreAssertions(hTest);
+ DnDTransferListDestroy(&list);
+
+ /* Empty. */
+ RTTEST_CHECK_RC(hTest, DnDTransferListInit(&list), VINF_SUCCESS);
+ DnDTransferListDestroy(&list);
+
+ /* Initial status. */
+ RTTEST_CHECK_RC(hTest, DnDTransferListInitEx(&list, szPathWellKnown, DNDTRANSFERLISTFMT_NATIVE), VINF_SUCCESS);
+ RTTEST_CHECK(hTest, DnDTransferListGetRootCount(&list) == 0);
+ RTTEST_CHECK(hTest, DnDTransferListObjCount(&list) == 0);
+ RTTEST_CHECK(hTest, DnDTransferListObjTotalBytes(&list) == 0);
+ RTTEST_CHECK(hTest, DnDTransferListObjGetFirst(&list) == NULL);
+ DnDTransferListDestroy(&list);
+
+ char szPathTest[RTPATH_MAX];
+
+ /* Root path handling. */
+ RTTEST_CHECK_RC(hTest, DnDTransferListInitEx(&list, szPathWellKnown, DNDTRANSFERLISTFMT_NATIVE), VINF_SUCCESS);
+ RTTEST_CHECK_RC(hTest, DnDTransferListAppendPath(&list, DNDTRANSFERLISTFMT_NATIVE, "/wrong/root/path", DNDTRANSFERLIST_FLAGS_NONE), VERR_INVALID_PARAMETER);
+ rc = RTPathJoin(szPathTest, sizeof(szPathTest), szPathWellKnown, "/non/existing");
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTTEST_CHECK_RC(hTest, DnDTransferListAppendPath(&list, DNDTRANSFERLISTFMT_NATIVE, szPathTest, DNDTRANSFERLIST_FLAGS_NONE), VERR_PATH_NOT_FOUND);
+ DnDTransferListDestroy(&list);
+
+ /* Adding native stuff. */
+ /* No root path set yet and non-recursive -> will set root path to szPathWellKnown, but without any entries added. */
+ RTTEST_CHECK_RC(hTest, DnDTransferListInitEx(&list, szPathWellKnown, DNDTRANSFERLISTFMT_NATIVE), VINF_SUCCESS);
+ RTTEST_CHECK_RC(hTest, DnDTransferListAppendPath(&list, DNDTRANSFERLISTFMT_NATIVE, szPathWellKnown, DNDTRANSFERLIST_FLAGS_NONE), VINF_SUCCESS);
+ RTTEST_CHECK(hTest, DnDTransferListGetRootCount(&list));
+ RTTEST_CHECK(hTest, DnDTransferListObjCount(&list));
+
+ /* Add szPathWellKnown again, this time recursively. */
+ RTTEST_CHECK_RC(hTest, DnDTransferListAppendPath(&list, DNDTRANSFERLISTFMT_NATIVE, szPathWellKnown, DNDTRANSFERLIST_FLAGS_RECURSIVE), VINF_SUCCESS);
+ RTTEST_CHECK(hTest, DnDTransferListGetRootCount(&list));
+ RTTEST_CHECK(hTest, DnDTransferListObjCount(&list));
+
+ char *pszString = NULL;
+ size_t cbString = 0;
+ RTTEST_CHECK_RC_OK(hTest, DnDTransferListGetRoots(&list, DNDTRANSFERLISTFMT_NATIVE, &pszString, &cbString));
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Roots:\n%s\n\n", pszString);
+ RTStrFree(pszString);
+
+ PDNDTRANSFEROBJECT pObj;
+ while ((pObj = DnDTransferListObjGetFirst(&list)))
+ {
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Obj: %s\n", DnDTransferObjectGetDestPath(pObj));
+ DnDTransferListObjRemoveFirst(&list);
+ }
+ DnDTransferListDestroy(&list);
+
+ char *pszBuf;
+ size_t cbBuf;
+
+ /* To URI data. */
+ RTTEST_CHECK_RC(hTest, DnDTransferListInitEx(&list, szPathWellKnownURI, DNDTRANSFERLISTFMT_URI), VINF_SUCCESS);
+ RTStrPrintf(szPathTest, sizeof(szPathTest), "%s/foo", szPathWellKnownURI);
+ RTTEST_CHECK_RC(hTest, DnDTransferListAppendPath(&list, DNDTRANSFERLISTFMT_URI, szPathWellKnownURI, DNDTRANSFERLIST_FLAGS_NONE), VINF_SUCCESS);
+ RTTEST_CHECK_RC(hTest, DnDTransferListAppendPath(&list, DNDTRANSFERLISTFMT_URI, szPathTest, DNDTRANSFERLIST_FLAGS_NONE), VERR_PATH_NOT_FOUND);
+ RTTEST_CHECK_RC(hTest, DnDTransferListGetRootsEx(&list, DNDTRANSFERLISTFMT_NATIVE, "" /* pszBasePath */, "\n", &pszBuf, &cbBuf), VINF_SUCCESS);
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Roots (native):\n%s\n", pszBuf);
+ RTStrFree(pszBuf);
+ RTTEST_CHECK_RC(hTest, DnDTransferListGetRootsEx(&list, DNDTRANSFERLISTFMT_URI, "" /* pszBasePath */, "\n", &pszBuf, &cbBuf), VINF_SUCCESS);
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Roots (URI):\n%s\n", pszBuf);
+ RTStrFree(pszBuf);
+ RTTEST_CHECK_RC(hTest, DnDTransferListGetRootsEx(&list, DNDTRANSFERLISTFMT_URI, "\\new\\base\\path", "\n", &pszBuf, &cbBuf), VINF_SUCCESS);
+ RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Roots (URI, new base):\n%s\n", pszBuf);
+ RTStrFree(pszBuf);
+ RTTEST_CHECK_RC(hTest, DnDTransferListGetRootsEx(&list, DNDTRANSFERLISTFMT_URI, "\\..\\invalid\\path", "\n", &pszBuf, &cbBuf), VERR_INVALID_PARAMETER);
+ DnDTransferListDestroy(&list);
+
+ /* From URI data. */
+#ifdef RT_OS_WINDOWS
+ RTStrPrintf(szPathTest, sizeof(szPathTest), "C:/Windows/");
+ const char szURI[] =
+ "file:///C:/Windows/System32/Boot/\r\n"
+ "file:///C:/Windows/System/\r\n";
+#else
+ RTStrPrintf(szPathTest, sizeof(szPathTest), "/usr/");
+ const char szURI[] =
+ "file:///usr/bin/\r\n"
+ "file:///usr/lib/\r\n";
+#endif
+
+ RTTEST_CHECK_RC(hTest, DnDTransferListAppendPathsFromBuffer(&list, DNDTRANSFERLISTFMT_URI, szURI, sizeof(szURI), "\r\n",
+ DNDTRANSFERLIST_FLAGS_NONE), VINF_SUCCESS);
+ RTTEST_CHECK(hTest, DnDTransferListGetRootCount(&list) == 2);
+ RTTEST_CHECK(hTest, RTPathCompare(DnDTransferListGetRootPathAbs(&list), szPathTest) == 0);
+
+ /* Validate returned lengths. */
+ pszBuf = NULL;
+ RTTEST_CHECK_RC(hTest, DnDTransferListGetRootsEx(&list, DNDTRANSFERLISTFMT_URI, "/base/", "\r\n", &pszBuf, &cbBuf), VINF_SUCCESS);
+ RTTEST_CHECK_MSG(hTest, RTStrCmp(pszBuf, "file:///base/bin/\r\nfile:///base/lib/\r\n") == 0, (hTest, "Got '%s'", pszBuf));
+ RTTEST_CHECK_MSG(hTest, cbBuf == strlen(pszBuf) + 1, (hTest, "Got %d, expected %d\n", cbBuf, strlen(pszBuf) + 1));
+ RTStrFree(pszBuf);
+
+ pszBuf = NULL;
+ RTTEST_CHECK_RC(hTest, DnDTransferListGetRootsEx(&list, DNDTRANSFERLISTFMT_NATIVE, "/base/", "\r\n", &pszBuf, &cbBuf), VINF_SUCCESS);
+ RTTEST_CHECK_MSG(hTest, RTStrCmp(pszBuf, "/base/bin/\r\n/base/lib/\r\n") == 0, (hTest, "Got '%s'", pszBuf));
+ RTTEST_CHECK_MSG(hTest, cbBuf == strlen(pszBuf) + 1, (hTest, "Got %d, expected %d\n", cbBuf, strlen(pszBuf) + 1));
+ RTStrFree(pszBuf);
+
+ /* Validate roots with a new base. */
+ pszBuf = NULL;
+ RTTEST_CHECK_RC(hTest, DnDTransferListGetRootsEx(&list, DNDTRANSFERLISTFMT_NATIVE, "/native/base/path", "\n", &pszBuf, &cbBuf), VINF_SUCCESS);
+ RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Roots (URI, new base):\n%s\n", pszBuf);
+ RTStrFree(pszBuf);
+
+ pszBuf = NULL;
+ RTTEST_CHECK_RC(hTest, DnDTransferListGetRootsEx(&list, DNDTRANSFERLISTFMT_NATIVE, "\\windows\\path", "\n", &pszBuf, &cbBuf), VINF_SUCCESS);
+ RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Roots (URI, new base):\n%s\n", pszBuf);
+ RTStrFree(pszBuf);
+
+ pszBuf = NULL;
+ RTTEST_CHECK_RC(hTest, DnDTransferListGetRootsEx(&list, DNDTRANSFERLISTFMT_NATIVE, "\\\\windows\\\\path", "\n", &pszBuf, &cbBuf), VINF_SUCCESS);
+ RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Roots (URI, new base):\n%s\n", pszBuf);
+ RTStrFree(pszBuf);
+
+ DnDTransferListDestroy(&list);
+ DnDTransferListDestroy(&list); /* Doing this twice here is intentional. */
+
+ return RTTestSummaryAndDestroy(hTest);
+}
+
diff --git a/src/VBox/GuestHost/DragAndDrop/testcase/tstDnDTransferObject.cpp b/src/VBox/GuestHost/DragAndDrop/testcase/tstDnDTransferObject.cpp
new file mode 100644
index 00000000..b6a09981
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/testcase/tstDnDTransferObject.cpp
@@ -0,0 +1,128 @@
+/* $Id: tstDnDTransferObject.cpp $ */
+/** @file
+ * DnD URI object (DNDTRANSFEROBJECT) tests.
+ */
+
+/*
+ * Copyright (C) 2020-2022 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 <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+
+#include <VBox/GuestHost/DragAndDrop.h>
+
+
+static void tstPaths(RTTEST hTest)
+{
+ RTTestSub(hTest, "Testing path handling");
+
+ char szBuf[64];
+
+ DNDTRANSFEROBJECT Obj;
+ RT_ZERO(Obj);
+
+ /*
+ * Initialization handling.
+ */
+ RTTEST_CHECK(hTest, DnDTransferObjectGetSourcePath(&Obj) == NULL);
+ RTTEST_CHECK_RC(hTest, DnDTransferObjectGetDestPathEx(&Obj, DNDTRANSFEROBJPATHSTYLE_TRANSPORT, szBuf, sizeof(szBuf)), VERR_NOT_FOUND);
+ RTTEST_CHECK(hTest, DnDTransferObjectGetMode(&Obj) == 0);
+ RTTEST_CHECK(hTest, DnDTransferObjectGetSize(&Obj) == 0);
+ RTTEST_CHECK(hTest, DnDTransferObjectGetProcessed(&Obj) == 0);
+ RTTEST_CHECK(hTest, DnDTransferObjectGetType(&Obj) == DNDTRANSFEROBJTYPE_UNKNOWN);
+
+ /*
+ * Paths handling.
+ */
+ RTTEST_CHECK_RC_OK(hTest, DnDTransferObjectInitEx(&Obj, DNDTRANSFEROBJTYPE_FILE, "", "/rel/path/to/dst"));
+ RTTestDisableAssertions(hTest);
+ RTTEST_CHECK_RC (hTest, DnDTransferObjectInitEx(&Obj, DNDTRANSFEROBJTYPE_FILE, "", "/rel/path/to/dst"), VERR_WRONG_ORDER);
+ RTTestRestoreAssertions(hTest);
+ DnDTransferObjectReset(&Obj);
+
+ RTTEST_CHECK_RC_OK(hTest, DnDTransferObjectInitEx(&Obj, DNDTRANSFEROBJTYPE_FILE, "/src/path1", "dst/path2"));
+ RTTEST_CHECK(hTest, RTStrCmp(DnDTransferObjectGetSourcePath(&Obj), "/src/path1/dst/path2") == 0);
+ RTTEST_CHECK(hTest, RTStrCmp(DnDTransferObjectGetDestPath(&Obj), "dst/path2") == 0);
+ RTTEST_CHECK(hTest, DnDTransferObjectGetDestPathEx(&Obj, DNDTRANSFEROBJPATHSTYLE_DOS, szBuf, sizeof(szBuf)) == VINF_SUCCESS
+ && RTStrCmp(szBuf, "dst\\path2") == 0);
+ DnDTransferObjectReset(&Obj);
+ RTTEST_CHECK_RC_OK(hTest, DnDTransferObjectInitEx(&Obj, DNDTRANSFEROBJTYPE_FILE, "", "dst/with/ending/slash/"));
+ RTTEST_CHECK(hTest, RTStrCmp(DnDTransferObjectGetDestPath(&Obj), "dst/with/ending/slash/") == 0);
+ RTTEST_CHECK(hTest, DnDTransferObjectGetDestPathEx(&Obj, DNDTRANSFEROBJPATHSTYLE_TRANSPORT, szBuf, sizeof(szBuf)) == VINF_SUCCESS
+ && RTStrCmp(szBuf, "dst/with/ending/slash/") == 0);
+ DnDTransferObjectReset(&Obj);
+ RTTEST_CHECK_RC_OK(hTest, DnDTransferObjectInitEx(&Obj, DNDTRANSFEROBJTYPE_DIRECTORY, "", "dst/path2"));
+ RTTEST_CHECK(hTest, RTStrCmp(DnDTransferObjectGetSourcePath(&Obj), "dst/path2/") == 0);
+ RTTEST_CHECK(hTest, RTStrCmp(DnDTransferObjectGetDestPath(&Obj), "dst/path2/") == 0);
+ DnDTransferObjectReset(&Obj);
+ RTTEST_CHECK_RC_OK(hTest, DnDTransferObjectInitEx(&Obj, DNDTRANSFEROBJTYPE_DIRECTORY, "", "dst\\to\\path2"));
+ RTTEST_CHECK(hTest, RTStrCmp(DnDTransferObjectGetSourcePath(&Obj), "dst/to/path2/") == 0);
+ RTTEST_CHECK(hTest, RTStrCmp(DnDTransferObjectGetDestPath(&Obj), "dst/to/path2/") == 0);
+ DnDTransferObjectReset(&Obj);
+ /* Test that the destination does not have a beginning slash. */
+ RTTEST_CHECK_RC_OK(hTest, DnDTransferObjectInitEx(&Obj, DNDTRANSFEROBJTYPE_DIRECTORY, "/src/path2", "/dst/to/path2/"));
+ RTTEST_CHECK(hTest, RTStrCmp(DnDTransferObjectGetSourcePath(&Obj), "/src/path2/dst/to/path2/") == 0);
+ RTTEST_CHECK(hTest, RTStrCmp(DnDTransferObjectGetDestPath(&Obj), "dst/to/path2/") == 0);
+ DnDTransferObjectReset(&Obj);
+ RTTEST_CHECK_RC_OK(hTest, DnDTransferObjectInitEx(&Obj, DNDTRANSFEROBJTYPE_DIRECTORY, "/src/path2", "//////dst/to/path2/"));
+ RTTEST_CHECK(hTest, RTStrCmp(DnDTransferObjectGetDestPath(&Obj), "dst/to/path2/") == 0);
+
+ /*
+ * Invalid stuff.
+ */
+ DnDTransferObjectReset(&Obj);
+ RTTestDisableAssertions(hTest);
+ RTTEST_CHECK(hTest, DnDTransferObjectInitEx(&Obj, DNDTRANSFEROBJTYPE_DIRECTORY, "/src/path3", "../../dst/path3") == VERR_INVALID_PARAMETER);
+ RTTEST_CHECK(hTest, DnDTransferObjectInitEx(&Obj, DNDTRANSFEROBJTYPE_DIRECTORY, "/src/../../path3", "dst/path3") == VERR_INVALID_PARAMETER);
+ RTTestRestoreAssertions(hTest);
+
+ /*
+ * Reset handling.
+ */
+ DnDTransferObjectReset(&Obj);
+ RTTEST_CHECK(hTest, DnDTransferObjectGetSourcePath(&Obj) == NULL);
+ RTTEST_CHECK(hTest, DnDTransferObjectGetDestPath(&Obj) == NULL);
+
+ DnDTransferObjectDestroy(&Obj);
+ DnDTransferObjectDestroy(&Obj); /* Doing this twice here is intentional. */
+}
+
+int main()
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstDnDTransferObject", &hTest);
+ if (rc)
+ return rc;
+ RTTestBanner(hTest);
+
+ tstPaths(hTest);
+
+ return RTTestSummaryAndDestroy(hTest);
+}
+
diff --git a/src/VBox/GuestHost/DragAndDrop/testcase/tstDragAndDropQt/Makefile.kmk b/src/VBox/GuestHost/DragAndDrop/testcase/tstDragAndDropQt/Makefile.kmk
new file mode 100644
index 00000000..9470c095
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/testcase/tstDragAndDropQt/Makefile.kmk
@@ -0,0 +1,53 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for tstDragAndDropQt.
+#
+
+#
+# Copyright (C) 2022 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
+
+#
+# Targets and units.
+#
+USES += qt5
+PROGRAMS.linux += tstDragAndDropQt # Just needed for Linux right now.
+
+#
+# tstDragAndDropQt
+#
+tstDragAndDropQt_TEMPLATE = VBOXQTGUIEXE
+tstDragAndDropQt_DEFS = UNIT_TEST TESTCASE
+tstDragAndDropQt_NAME = tstDragAndDropQt
+tstDragAndDropQt_CXXFLAGS = \
+ $(VBOX_GCC_Wno-implicit-fallthrough) \
+ $(VBOX_GCC_Wno-deprecated-declarations)
+
+tstDragAndDropQt_SOURCES = \
+ tstDragAndDropQt.cpp
+
+# The Qt modules we're using.
+tstDragAndDropQt_QT_MODULES = Core Gui Widgets PrintSupport
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/GuestHost/DragAndDrop/testcase/tstDragAndDropQt/tstDragAndDropQt.cpp b/src/VBox/GuestHost/DragAndDrop/testcase/tstDragAndDropQt/tstDragAndDropQt.cpp
new file mode 100644
index 00000000..6fd5ad4c
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/testcase/tstDragAndDropQt/tstDragAndDropQt.cpp
@@ -0,0 +1,67 @@
+/* $Id: tstDragAndDropQt.cpp $ */
+/** @file
+ * Drag and drop Qt code test cases.
+ */
+
+/*
+ * Copyright (C) 2022 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 <QtWidgets>
+
+class DnDWin : public QWidget
+{
+ public:
+
+ explicit DnDWin(QWidget *parent = nullptr) : QWidget(parent)
+ {
+ setMinimumSize(400, 400);
+ setWindowTitle("Drag me!");
+ setAcceptDrops(true);
+ }
+
+ void mouseMoveEvent(QMouseEvent *event)
+ {
+ if(!(event->buttons() & Qt::LeftButton))
+ return DnDWin::mouseMoveEvent(event);
+
+ event->accept();
+
+ QDrag *drag = new QDrag(this);
+
+ QMimeData *mime = new QMimeData();
+ mime->setData("text/plain", QString("/tmp/%1").arg("foo.bar").toLatin1());
+ mime->setData("text/uri-list", QString("file:///tmp/%1").arg("foo.bar").toLatin1());
+
+ drag->setMimeData(mime);
+ drag->exec();
+ }
+};
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+
+ DnDWin win;
+ win.show();
+
+ app.exec();
+}
diff --git a/src/VBox/GuestHost/HGSMI/.scm-settings b/src/VBox/GuestHost/HGSMI/.scm-settings
new file mode 100644
index 00000000..389766a6
--- /dev/null
+++ b/src/VBox/GuestHost/HGSMI/.scm-settings
@@ -0,0 +1,31 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for HGSMI.
+#
+
+#
+# Copyright (C) 2010-2022 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
+#
+
+# @bugref{8524} r114561: MIT license of files upstreamed to the Linux kernel to
+# encourage other kernels to pick them up.
+HGSMICommon.cpp: --license-mit
+
diff --git a/src/VBox/GuestHost/HGSMI/HGSMICommon.cpp b/src/VBox/GuestHost/HGSMI/HGSMICommon.cpp
new file mode 100644
index 00000000..019c0513
--- /dev/null
+++ b/src/VBox/GuestHost/HGSMI/HGSMICommon.cpp
@@ -0,0 +1,450 @@
+/* $Id: HGSMICommon.cpp $ */
+/** @file
+ * VBox Host Guest Shared Memory Interface (HGSMI) - Functions common to both host and guest.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#define LOG_DISABLED /* Maybe we can enabled it all the time now? */
+/** @note commented out all logging statements to avoid pulling the logging
+ * sub-system into places like the Linux kernel driver. Perhaps the best
+ * thing would be to use return enough information for callers to log what
+ * is needed. */
+#define LOG_GROUP LOG_GROUP_HGSMI
+
+#include <VBoxVideoIPRT.h>
+
+#include <HGSMI.h>
+// #include <VBox/log.h>
+
+
+/* Channel flags. */
+#define HGSMI_CH_F_REGISTERED 0x01
+
+/* Assertions for situations which could happen and normally must be processed properly
+ * but must be investigated during development: guest misbehaving, etc.
+ */
+#ifdef HGSMI_STRICT
+#define HGSMI_STRICT_ASSERT_FAILED() AssertFailed()
+#define HGSMI_STRICT_ASSERT(expr) Assert(expr)
+#else
+#define HGSMI_STRICT_ASSERT_FAILED() do {} while (0)
+#define HGSMI_STRICT_ASSERT(expr) do {} while (0)
+#endif /* !HGSMI_STRICT */
+
+/*
+ * We do not want assertions in Linux kernel code to reduce symbol dependencies.
+ */
+#if defined(IN_RING0) && defined(RT_OS_LINUX)
+# define HGSMI_ASSERT_PTR_RETURN(a, b) if (!(a)) return (b)
+#else
+# define HGSMI_ASSERT_PTR_RETURN(a, b) if (!(a)) return (b)
+#endif /* !IN_RING0 && RT_OS_LINUX */
+
+/* One-at-a-Time Hash from
+ * http://www.burtleburtle.net/bob/hash/doobs.html
+ *
+ * ub4 one_at_a_time(char *key, ub4 len)
+ * {
+ * ub4 hash, i;
+ * for (hash=0, i=0; i<len; ++i)
+ * {
+ * hash += key[i];
+ * hash += (hash << 10);
+ * hash ^= (hash >> 6);
+ * }
+ * hash += (hash << 3);
+ * hash ^= (hash >> 11);
+ * hash += (hash << 15);
+ * return hash;
+ * }
+ */
+
+static uint32_t hgsmiHashBegin(void)
+{
+ return 0;
+}
+
+static uint32_t hgsmiHashProcess(uint32_t hash, const void RT_UNTRUSTED_VOLATILE_HSTGST *pvData, size_t cbData)
+{
+ const uint8_t *pu8Data = (const uint8_t *)pvData;
+
+ while (cbData--)
+ {
+ hash += *pu8Data++;
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+
+ return hash;
+}
+
+static uint32_t hgsmiHashEnd(uint32_t hash)
+{
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+
+ return hash;
+}
+
+uint32_t HGSMIChecksum(HGSMIOFFSET offBuffer, const HGSMIBUFFERHEADER RT_UNTRUSTED_VOLATILE_HSTGST *pHeader,
+ const HGSMIBUFFERTAIL RT_UNTRUSTED_VOLATILE_HSTGST *pTail)
+{
+ uint32_t u32Checksum = hgsmiHashBegin();
+
+ u32Checksum = hgsmiHashProcess(u32Checksum, &offBuffer, sizeof(offBuffer));
+ u32Checksum = hgsmiHashProcess(u32Checksum, pHeader, sizeof(HGSMIBUFFERHEADER));
+ u32Checksum = hgsmiHashProcess(u32Checksum, pTail, RT_UOFFSETOF(HGSMIBUFFERTAIL, u32Checksum));
+
+ return hgsmiHashEnd(u32Checksum);
+}
+
+int HGSMIAreaInitialize(HGSMIAREA *pArea, void *pvBase, HGSMISIZE cbArea, HGSMIOFFSET offBase)
+{
+ uint8_t *pu8Base = (uint8_t *)pvBase;
+
+ if ( !pArea /* Check that the area: */
+ || cbArea < HGSMIBufferMinimumSize() /* large enough; */
+ || pu8Base + cbArea < pu8Base /* no address space wrap; */
+ || offBase > UINT32_C(0xFFFFFFFF) - cbArea /* area within the 32 bit space: offBase + cbMem <= 0xFFFFFFFF. */
+ )
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ pArea->pu8Base = pu8Base;
+ pArea->offBase = offBase;
+ pArea->offLast = cbArea - HGSMIBufferMinimumSize() + offBase;
+ pArea->cbArea = cbArea;
+
+ return VINF_SUCCESS;
+}
+
+void HGSMIAreaClear(HGSMIAREA *pArea)
+{
+ if (pArea)
+ {
+ RT_ZERO(*pArea);
+ }
+}
+
+/* Initialize the memory buffer including its checksum.
+ * No changes alloed to the header and the tail after that.
+ */
+HGSMIOFFSET HGSMIBufferInitializeSingle(const HGSMIAREA *pArea,
+ HGSMIBUFFERHEADER *pHeader,
+ HGSMISIZE cbBuffer,
+ uint8_t u8Channel,
+ uint16_t u16ChannelInfo)
+{
+ if ( !pArea
+ || !pHeader
+ || cbBuffer < HGSMIBufferMinimumSize())
+ {
+ return HGSMIOFFSET_VOID;
+ }
+
+ /* Buffer must be within the area:
+ * * header data size do not exceed the maximum data size;
+ * * buffer address is greater than the area base address;
+ * * buffer address is lower than the maximum allowed for the given data size.
+ */
+ HGSMISIZE cbMaximumDataSize = pArea->offLast - pArea->offBase;
+ uint32_t u32DataSize = cbBuffer - HGSMIBufferMinimumSize();
+
+ if ( u32DataSize > cbMaximumDataSize
+ || (uint8_t *)pHeader < pArea->pu8Base
+ || (uint8_t *)pHeader > pArea->pu8Base + cbMaximumDataSize - u32DataSize)
+ {
+ return HGSMIOFFSET_VOID;
+ }
+
+ HGSMIOFFSET offBuffer = HGSMIPointerToOffset(pArea, pHeader);
+
+ pHeader->u8Flags = HGSMI_BUFFER_HEADER_F_SEQ_SINGLE;
+ pHeader->u32DataSize = u32DataSize;
+ pHeader->u8Channel = u8Channel;
+ pHeader->u16ChannelInfo = u16ChannelInfo;
+ RT_ZERO(pHeader->u.au8Union);
+
+ HGSMIBUFFERTAIL RT_UNTRUSTED_VOLATILE_HSTGST *pTail = HGSMIBufferTailFromPtr(pHeader, u32DataSize);
+ pTail->u32Reserved = 0;
+ pTail->u32Checksum = HGSMIChecksum(offBuffer, pHeader, pTail);
+
+ return offBuffer;
+}
+
+int HGSMIHeapSetup(HGSMIHEAP *pHeap, void *pvBase, HGSMISIZE cbArea, HGSMIOFFSET offBase, const HGSMIENV *pEnv)
+{
+ HGSMI_ASSERT_PTR_RETURN(pHeap, VERR_INVALID_PARAMETER);
+ HGSMI_ASSERT_PTR_RETURN(pvBase, VERR_INVALID_PARAMETER);
+
+ int rc = HGSMIAreaInitialize(&pHeap->area, pvBase, cbArea, offBase);
+ if (RT_SUCCESS(rc))
+ {
+ rc = HGSMIMAInit(&pHeap->ma, &pHeap->area, NULL, 0, 0, pEnv);
+ if (RT_FAILURE(rc))
+ {
+ HGSMIAreaClear(&pHeap->area);
+ }
+ }
+
+ return rc;
+}
+
+void HGSMIHeapDestroy(HGSMIHEAP *pHeap)
+{
+ if (pHeap)
+ {
+ HGSMIMAUninit(&pHeap->ma);
+ RT_ZERO(*pHeap);
+ }
+}
+
+void RT_UNTRUSTED_VOLATILE_HOST *HGSMIHeapAlloc(HGSMIHEAP *pHeap,
+ HGSMISIZE cbData,
+ uint8_t u8Channel,
+ uint16_t u16ChannelInfo)
+{
+ HGSMISIZE cbAlloc = HGSMIBufferRequiredSize(cbData);
+ HGSMIBUFFERHEADER *pHeader = (HGSMIBUFFERHEADER *)HGSMIHeapBufferAlloc(pHeap, cbAlloc);
+ if (pHeader)
+ {
+ HGSMIOFFSET offBuffer = HGSMIBufferInitializeSingle(HGSMIHeapArea(pHeap), pHeader,
+ cbAlloc, u8Channel, u16ChannelInfo);
+ if (offBuffer == HGSMIOFFSET_VOID)
+ {
+ HGSMIHeapBufferFree(pHeap, pHeader);
+ pHeader = NULL;
+ }
+ }
+
+ return pHeader? HGSMIBufferDataFromPtr(pHeader): NULL;
+}
+
+void HGSMIHeapFree(HGSMIHEAP *pHeap, void RT_UNTRUSTED_VOLATILE_GUEST *pvData)
+{
+ if (pvData)
+ {
+ HGSMIBUFFERHEADER RT_UNTRUSTED_VOLATILE_HOST *pHeader = HGSMIBufferHeaderFromData(pvData);
+ HGSMIHeapBufferFree(pHeap, pHeader);
+ }
+}
+
+void RT_UNTRUSTED_VOLATILE_HSTGST *HGSMIHeapBufferAlloc(HGSMIHEAP *pHeap, HGSMISIZE cbBuffer)
+{
+ return HGSMIMAAlloc(&pHeap->ma, cbBuffer);
+}
+
+void HGSMIHeapBufferFree(HGSMIHEAP *pHeap, void RT_UNTRUSTED_VOLATILE_GUEST *pvBuf)
+{
+ HGSMIMAFree(&pHeap->ma, pvBuf);
+}
+
+typedef struct HGSMIBUFFERCONTEXT
+{
+ /** The original buffer header. */
+ const HGSMIBUFFERHEADER RT_UNTRUSTED_VOLATILE_HSTGST *pHeader;
+ /** Payload data in the buffer. */
+ void RT_UNTRUSTED_VOLATILE_HSTGST *pvData;
+ /** Size of data */
+ uint32_t cbData;
+} HGSMIBUFFERCONTEXT;
+
+/** Verify that the given offBuffer points to a valid buffer, which is within the area.
+ *
+ * @returns VBox status and the buffer information in pBufferContext.
+ * @param pArea Area which supposed to contain the buffer.
+ * @param offBuffer The buffer location in the area.
+ * @param pBufferContext Where to write information about the buffer.
+ */
+static int hgsmiVerifyBuffer(const HGSMIAREA *pArea, HGSMIOFFSET offBuffer, HGSMIBUFFERCONTEXT *pBufferContext)
+{
+ // LogFlowFunc(("buffer 0x%x, area %p %x [0x%x;0x%x]\n",
+ // offBuffer, pArea->pu8Base, pArea->cbArea, pArea->offBase, pArea->offLast));
+
+ int rc = VINF_SUCCESS;
+
+ if ( offBuffer < pArea->offBase
+ || offBuffer > pArea->offLast)
+ {
+ // LogFunc(("offset 0x%x is outside the area [0x%x;0x%x]!!!\n",
+ // offBuffer, pArea->offBase, pArea->offLast));
+ rc = VERR_INVALID_PARAMETER;
+ HGSMI_STRICT_ASSERT_FAILED();
+ }
+ else
+ {
+ void RT_UNTRUSTED_VOLATILE_HSTGST *pvBuffer = HGSMIOffsetToPointer(pArea, offBuffer);
+ HGSMIBUFFERHEADER header;
+ memcpy(&header, (void *)HGSMIBufferHeaderFromPtr(pvBuffer), sizeof(header));
+ ASMCompilerBarrier();
+
+ /* Quick check of the data size, it should be less than the maximum
+ * data size for the buffer at this offset.
+ */
+ // LogFlowFunc(("datasize check: header.u32DataSize = 0x%x pArea->offLast - offBuffer = 0x%x\n",
+ // header.u32DataSize, pArea->offLast - offBuffer));
+
+ if (header.u32DataSize <= pArea->offLast - offBuffer)
+ {
+ HGSMIBUFFERTAIL tail;
+ memcpy(&tail, (void *)HGSMIBufferTailFromPtr(pvBuffer, header.u32DataSize), sizeof(tail));
+ ASMCompilerBarrier();
+
+ /* At least both header and tail structures are in the area. Check the checksum. */
+ uint32_t u32Checksum = HGSMIChecksum(offBuffer, &header, &tail);
+ // LogFlowFunc(("checksum check: u32Checksum = 0x%x pTail->u32Checksum = 0x%x\n",
+ // u32Checksum, tail.u32Checksum));
+ if (u32Checksum == tail.u32Checksum)
+ {
+ /* Success. */
+ pBufferContext->pHeader = HGSMIBufferHeaderFromPtr(pvBuffer);
+ pBufferContext->pvData = HGSMIBufferDataFromPtr(pvBuffer);
+ pBufferContext->cbData = header.u32DataSize;
+ }
+ else
+ {
+ // LogFunc(("invalid checksum 0x%x, expected 0x%x!!!\n",
+ // u32Checksum, tail.u32Checksum));
+ rc = VERR_INVALID_STATE;
+ HGSMI_STRICT_ASSERT_FAILED();
+ }
+ }
+ else
+ {
+ // LogFunc(("invalid data size 0x%x, maximum is 0x%x!!!\n",
+ // header.u32DataSize, pArea->offLast - offBuffer));
+ rc = VERR_TOO_MUCH_DATA;
+ HGSMI_STRICT_ASSERT_FAILED();
+ }
+ }
+
+ return rc;
+}
+
+/** Helper to convert HGSMI channel index to the channel structure pointer.
+ *
+ * @returns Pointer to the channel data.
+ * @param pChannelInfo The channel pool.
+ * @param u8Channel The channel index.
+ */
+HGSMICHANNEL *HGSMIChannelFindById(HGSMICHANNELINFO *pChannelInfo,
+ uint8_t u8Channel)
+{
+ AssertCompile(RT_ELEMENTS(pChannelInfo->Channels) >= 0x100);
+ HGSMICHANNEL *pChannel = &pChannelInfo->Channels[u8Channel];
+
+ if (pChannel->u8Flags & HGSMI_CH_F_REGISTERED)
+ {
+ return pChannel;
+ }
+
+ return NULL;
+}
+
+/** Process a guest buffer.
+ *
+ * @returns VBox status code.
+ * @param pArea Area which supposed to contain the buffer.
+ * @param pChannelInfo The channel pool.
+ * @param offBuffer The buffer location in the area.
+ */
+int HGSMIBufferProcess(const HGSMIAREA *pArea,
+ HGSMICHANNELINFO *pChannelInfo,
+ HGSMIOFFSET offBuffer)
+{
+ // LogFlowFunc(("pArea %p, offBuffer 0x%x\n", pArea, offBuffer));
+
+ HGSMI_ASSERT_PTR_RETURN(pArea, VERR_INVALID_PARAMETER);
+ HGSMI_ASSERT_PTR_RETURN(pChannelInfo, VERR_INVALID_PARAMETER);
+
+ /* Guest has prepared a command description at 'offBuffer'. */
+ HGSMIBUFFERCONTEXT bufferContext = { NULL, NULL, 0 }; /* Makes old GCC happier. */
+ int rc = hgsmiVerifyBuffer(pArea, offBuffer, &bufferContext);
+ if (RT_SUCCESS(rc))
+ {
+ /* Pass the command to the appropriate handler registered with this instance.
+ * Start with the handler list head, which is the preallocated HGSMI setup channel.
+ */
+ const HGSMICHANNEL *pChannel = HGSMIChannelFindById(pChannelInfo, bufferContext.pHeader->u8Channel);
+ if (pChannel)
+ {
+ const HGSMICHANNELHANDLER *pHandler = &pChannel->handler;
+ if (pHandler->pfnHandler)
+ {
+ pHandler->pfnHandler(pHandler->pvHandler, bufferContext.pHeader->u16ChannelInfo,
+ bufferContext.pvData, bufferContext.cbData);
+ }
+ HGSMI_STRICT_ASSERT(RT_SUCCESS(hgsmiVerifyBuffer(pArea, offBuffer, &bufferContext)));
+ }
+ else
+ {
+ rc = VERR_INVALID_FUNCTION;
+ HGSMI_STRICT_ASSERT_FAILED();
+ }
+ }
+
+ return rc;
+}
+
+/** Register a new HGSMI channel by index.
+ *
+ * @returns VBox status code.
+ * @param pChannelInfo The channel pool managed by the caller.
+ * @param u8Channel Index of the channel.
+ * @param pszName Name of the channel (optional, allocated by the caller).
+ * @param pfnChannelHandler The channel callback.
+ * @param pvChannelHandler The callback pointer.
+ */
+int HGSMIChannelRegister(HGSMICHANNELINFO *pChannelInfo,
+ uint8_t u8Channel,
+ const char *pszName,
+ PFNHGSMICHANNELHANDLER pfnChannelHandler,
+ void *pvChannelHandler)
+{
+ /* Check whether the channel is already registered. */
+ HGSMICHANNEL *pChannel = HGSMIChannelFindById(pChannelInfo, u8Channel);
+ if (pChannel)
+ {
+ HGSMI_STRICT_ASSERT_FAILED();
+ return VERR_ALREADY_EXISTS;
+ }
+
+ /* Channel is not yet registered. */
+ pChannel = &pChannelInfo->Channels[u8Channel];
+
+ pChannel->u8Flags = HGSMI_CH_F_REGISTERED;
+ pChannel->u8Channel = u8Channel;
+
+ pChannel->handler.pfnHandler = pfnChannelHandler;
+ pChannel->handler.pvHandler = pvChannelHandler;
+
+ pChannel->pszName = pszName;
+
+ return VINF_SUCCESS;
+}
diff --git a/src/VBox/GuestHost/HGSMI/HGSMIMemAlloc.cpp b/src/VBox/GuestHost/HGSMI/HGSMIMemAlloc.cpp
new file mode 100644
index 00000000..1e211864
--- /dev/null
+++ b/src/VBox/GuestHost/HGSMI/HGSMIMemAlloc.cpp
@@ -0,0 +1,684 @@
+/* $Id: HGSMIMemAlloc.cpp $ */
+/** @file
+ * VBox Host Guest Shared Memory Interface (HGSMI) - Memory allocator.
+ */
+
+/*
+ * Copyright (C) 2014-2022 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
+ */
+
+/*
+ * Memory allocator
+ * ----------------
+ *
+ * Area [0; AreaSize) contains only the data, control structures are separate.
+ * Block sizes are power of 2: 32B, ..., 1MB
+ * Area size can be anything and will be divided initially to largest possible free blocks.
+ *
+ * The entire area is described by a list of 32 bit block descriptors:
+ * * bits 0..3 - order, which is log2 size of the block - 5: 2^(0+5) ... 2^(15+5) == 32B .. 1MB
+ * * bit 4 - 1 for free blocks.
+ * * bits 5..31 - block offset.
+ *
+ * 31 ... 5 | 4 | 3 ... 0
+ * offset F order
+ *
+ * There is a sorted collection of all block descriptors
+ * (key is the block offset, bits 0...4 do not interfere with sorting).
+ * Also there are lists of free blocks for each size for fast allocation.
+ *
+ *
+ * Implementation
+ * --------------
+ *
+ * The blocks collection is a sorted linear list.
+ *
+ * Initially the entire area consists of one or more largest blocks followed by smaller blocks:
+ * * 100B area - 64B block with descriptor: 0x00000011
+ * 32B block with descriptor: 0x00000030
+ * 4B unused
+ * * 64K area - one 64K block with descriptor: 0x0000001C
+ * * 512K area - one 512K block with descriptor: 0x0000001F
+ *
+ * When allocating a new block:
+ * * larger free blocks are splitted when there are no smaller free blocks;
+ * * smaller free blocks are merged if they can build a requested larger block.
+ */
+#include <HGSMIMemAlloc.h>
+#include <HGSMI.h>
+
+#include <VBoxVideoIPRT.h>
+
+/*
+ * We do not want assertions in Linux kernel code to reduce symbol dependencies.
+ */
+#if defined(IN_RING0) && defined(RT_OS_LINUX)
+# define HGSMI_ASSERT_RETURN(a, b) if (!(a)) return (b)
+# define HGSMI_ASSERT_FAILED() do {} while (0)
+# define HGSMI_ASSERT(expr) do {} while (0)
+#else
+# define HGSMI_ASSERT_RETURN(a, b) AssertReturn(a, b)
+# define HGSMI_ASSERT_FAILED() AssertFailed()
+# define HGSMI_ASSERT(expr) Assert(expr)
+#endif /* !IN_RING0 && RT_OS_LINUX */
+
+DECLINLINE(HGSMIOFFSET) hgsmiMADescriptor(HGSMIOFFSET off, bool fFree, HGSMIOFFSET order)
+{
+ return (off & HGSMI_MA_DESC_OFFSET_MASK) |
+ (fFree? HGSMI_MA_DESC_FREE_MASK: 0) |
+ (order & HGSMI_MA_DESC_ORDER_MASK);
+}
+
+static void hgsmiMABlockFree(HGSMIMADATA *pMA, HGSMIMABLOCK *pBlock)
+{
+ pMA->env.pfnFree(pMA->env.pvEnv, pBlock);
+}
+
+static int hgsmiMABlockAlloc(HGSMIMADATA *pMA, HGSMIMABLOCK **ppBlock)
+{
+ int rc = VINF_SUCCESS;
+
+ HGSMIMABLOCK *pBlock = (HGSMIMABLOCK *)pMA->env.pfnAlloc(pMA->env.pvEnv, sizeof(HGSMIMABLOCK));
+ if (pBlock)
+ {
+ RT_ZERO(pBlock->nodeBlock);
+ *ppBlock = pBlock;
+ }
+ else
+ {
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+/* Divide entire area to free blocks. */
+static int hgsmiMAFormat(HGSMIMADATA *pMA)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Initial value, it will be updated in the loop below. */
+ pMA->cbMaxBlock = HGSMI_MA_BLOCK_SIZE_MIN;
+ pMA->cBlocks = 0;
+
+ HGSMISIZE cbBlock = HGSMI_MA_BLOCK_SIZE_MAX;
+ HGSMISIZE cbRemaining = pMA->area.cbArea;
+ HGSMIOFFSET off = 0;
+
+ while (cbBlock >= HGSMI_MA_BLOCK_SIZE_MIN)
+ {
+ /* Build a list of free memory blocks with u32BlockSize. */
+ uint32_t cBlocks = cbRemaining / cbBlock;
+ if (cBlocks > 0)
+ {
+ if (pMA->cbMaxBlock < cbBlock)
+ {
+ pMA->cbMaxBlock = cbBlock;
+ }
+
+ HGSMIOFFSET order = HGSMIMASize2Order(cbBlock);
+
+ uint32_t i;
+ for (i = 0; i < cBlocks; ++i)
+ {
+ /* A new free block. */
+ HGSMIMABLOCK *pBlock;
+ rc = hgsmiMABlockAlloc(pMA, &pBlock);
+ if (RT_FAILURE(rc))
+ {
+ break;
+ }
+
+ pBlock->descriptor = hgsmiMADescriptor(off, true, order);
+ RTListAppend(&pMA->listBlocks, &pBlock->nodeBlock);
+ ++pMA->cBlocks;
+
+ off += cbBlock;
+ cbRemaining -= cbBlock;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ break;
+ }
+
+ cbBlock /= 2;
+ }
+
+ return rc;
+}
+
+static int hgsmiMARebuildFreeLists(HGSMIMADATA *pMA)
+{
+ int rc = VINF_SUCCESS;
+
+ HGSMIMABLOCK *pIter;
+ RTListForEach(&pMA->listBlocks, pIter, HGSMIMABLOCK, nodeBlock)
+ {
+ if (HGSMI_MA_DESC_IS_FREE(pIter->descriptor))
+ {
+ HGSMIOFFSET order = HGSMI_MA_DESC_ORDER(pIter->descriptor);
+ RTListAppend(&pMA->aListFreeBlocks[order], &pIter->nodeFree);
+ }
+ }
+
+ return rc;
+}
+
+static int hgsmiMARestore(HGSMIMADATA *pMA, HGSMIOFFSET *paDescriptors, uint32_t cDescriptors, HGSMISIZE cbMaxBlock)
+{
+ int rc = VINF_SUCCESS;
+
+ pMA->cbMaxBlock = cbMaxBlock;
+ pMA->cBlocks = 0;
+
+ HGSMISIZE cbRemaining = pMA->area.cbArea;
+ HGSMIOFFSET off = 0;
+
+ uint32_t i;
+ for (i = 0; i < cDescriptors; ++i)
+ {
+ /* Verify the descriptor. */
+ HGSMISIZE cbBlock = HGSMIMAOrder2Size(HGSMI_MA_DESC_ORDER(paDescriptors[i]));
+ if ( off != HGSMI_MA_DESC_OFFSET(paDescriptors[i])
+ || cbBlock > cbRemaining
+ || cbBlock > pMA->cbMaxBlock)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ /* A new free block. */
+ HGSMIMABLOCK *pBlock;
+ rc = hgsmiMABlockAlloc(pMA, &pBlock);
+ if (RT_FAILURE(rc))
+ {
+ break;
+ }
+
+ pBlock->descriptor = paDescriptors[i];
+ RTListAppend(&pMA->listBlocks, &pBlock->nodeBlock);
+ ++pMA->cBlocks;
+
+ off += cbBlock;
+ cbRemaining -= cbBlock;
+ }
+
+ return rc;
+}
+
+static HGSMIMABLOCK *hgsmiMAGetFreeBlock(HGSMIMADATA *pMA, HGSMIOFFSET order)
+{
+ HGSMIMABLOCK *pBlock = NULL;
+
+ HGSMIOFFSET i;
+ for (i = order; i < RT_ELEMENTS(pMA->aListFreeBlocks); ++i)
+ {
+ pBlock = RTListGetFirst(&pMA->aListFreeBlocks[i], HGSMIMABLOCK, nodeFree);
+ if (pBlock)
+ {
+ break;
+ }
+ }
+
+ if (pBlock)
+ {
+ HGSMI_ASSERT_RETURN(HGSMI_MA_DESC_IS_FREE(pBlock->descriptor), NULL);
+
+ /* Where the block starts. */
+ HGSMIOFFSET off = HGSMI_MA_DESC_OFFSET(pBlock->descriptor);
+
+ /* 'i' is the order of the block. */
+ while (i != order)
+ {
+ /* A larger block was found and need to be split to 2 smaller blocks. */
+ HGSMIMABLOCK *pBlock2;
+ int rc = hgsmiMABlockAlloc(pMA, &pBlock2);
+ if (RT_FAILURE(rc))
+ {
+ pBlock = NULL;
+ break;
+ }
+
+ /* Create 2 blocks with descreased order. */
+ --i;
+
+ /* Remove from the free list. */
+ RTListNodeRemove(&pBlock->nodeFree);
+
+ pBlock->descriptor = hgsmiMADescriptor(off, true, i);
+ pBlock2->descriptor = hgsmiMADescriptor(off + HGSMIMAOrder2Size(i), true, i);
+
+ /* Update list of all blocks by inserting pBlock2 after pBlock. */
+ RTListNodeInsertAfter(&pBlock->nodeBlock, &pBlock2->nodeBlock);
+ ++pMA->cBlocks;
+
+ /* Update the free list. */
+ RTListAppend(&pMA->aListFreeBlocks[i], &pBlock->nodeFree);
+ RTListAppend(&pMA->aListFreeBlocks[i], &pBlock2->nodeFree);
+ }
+ }
+
+ return pBlock;
+}
+
+static void hgsmiMAReformatFreeBlocks(HGSMIMADATA *pMA, HGSMIOFFSET maxId,
+ HGSMIMABLOCK *pStart, HGSMIMABLOCK *pEnd, HGSMISIZE cbBlocks)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Blocks starting from pStart until pEnd will be replaced with
+ * another set of blocks.
+ *
+ * The new set will include the block with the required order.
+ * Since the required order is larger than any existing block,
+ * it will replace at least two existing blocks.
+ * The new set will also have minimal possible number of blocks.
+ * Therefore the new set will have at least one block less.
+ * Blocks will be updated in place and remaining blocks will be
+ * deallocated.
+ */
+
+ HGSMISIZE u32BlockSize = HGSMIMAOrder2Size(maxId);
+ HGSMISIZE cbRemaining = cbBlocks;
+ HGSMIOFFSET off = HGSMI_MA_DESC_OFFSET(pStart->descriptor);
+ HGSMIMABLOCK *pBlock = pStart;
+
+ while (u32BlockSize >= HGSMI_MA_BLOCK_SIZE_MIN && cbRemaining)
+ {
+ /* Build a list of free memory blocks with u32BlockSize. */
+ uint32_t cBlocks = cbRemaining / u32BlockSize;
+ if (cBlocks > 0)
+ {
+ HGSMIOFFSET order = HGSMIMASize2Order(u32BlockSize);
+
+ uint32_t i;
+ for (i = 0; i < cBlocks; ++i)
+ {
+ if (pBlock == pEnd)
+ {
+ /* Should never happen because the new set of blocks is supposed to be smaller. */
+ HGSMI_ASSERT_FAILED();
+ rc = VERR_OUT_OF_RESOURCES;
+ break;
+ }
+
+ /* Remove from the free list. */
+ RTListNodeRemove(&pBlock->nodeFree);
+
+ pBlock->descriptor = hgsmiMADescriptor(off, true, order);
+
+ RTListAppend(&pMA->aListFreeBlocks[order], &pBlock->nodeFree);
+
+ off += u32BlockSize;
+ cbRemaining -= u32BlockSize;
+
+ pBlock = RTListGetNext(&pMA->listBlocks, pBlock, HGSMIMABLOCK, nodeBlock);
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ break;
+ }
+
+ u32BlockSize /= 2;
+ }
+
+ HGSMI_ASSERT(cbRemaining == 0);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Remove remaining free blocks from pBlock until pEnd */
+ for (;;)
+ {
+ bool fEnd = (pBlock == pEnd);
+ HGSMIMABLOCK *pNext = RTListGetNext(&pMA->listBlocks, pBlock, HGSMIMABLOCK, nodeBlock);
+
+ RTListNodeRemove(&pBlock->nodeFree);
+ RTListNodeRemove(&pBlock->nodeBlock);
+ --pMA->cBlocks;
+
+ hgsmiMABlockFree(pMA, pBlock);
+
+ if (fEnd)
+ {
+ break;
+ }
+
+ pBlock = pNext;
+ }
+ }
+}
+
+static void hgsmiMAQueryFreeRange(HGSMIMADATA *pMA, HGSMIMABLOCK *pBlock, HGSMISIZE cbRequired,
+ HGSMIMABLOCK **ppStart, HGSMIMABLOCK **ppEnd, HGSMISIZE *pcbBlocks)
+{
+ HGSMI_ASSERT(HGSMI_MA_DESC_IS_FREE(pBlock->descriptor));
+
+ *pcbBlocks = HGSMIMAOrder2Size(HGSMI_MA_DESC_ORDER(pBlock->descriptor));
+ *ppStart = pBlock;
+ *ppEnd = pBlock;
+
+ HGSMIMABLOCK *p;
+ for (;;)
+ {
+ p = RTListGetNext(&pMA->listBlocks, *ppEnd, HGSMIMABLOCK, nodeBlock);
+ if (!p || !HGSMI_MA_DESC_IS_FREE(p->descriptor))
+ {
+ break;
+ }
+ *pcbBlocks += HGSMIMAOrder2Size(HGSMI_MA_DESC_ORDER(p->descriptor));
+ *ppEnd = p;
+
+ if (cbRequired && *pcbBlocks >= cbRequired)
+ {
+ return;
+ }
+ }
+ for (;;)
+ {
+ p = RTListGetPrev(&pMA->listBlocks, *ppStart, HGSMIMABLOCK, nodeBlock);
+ if (!p || !HGSMI_MA_DESC_IS_FREE(p->descriptor))
+ {
+ break;
+ }
+ *pcbBlocks += HGSMIMAOrder2Size(HGSMI_MA_DESC_ORDER(p->descriptor));
+ *ppStart = p;
+
+ if (cbRequired && *pcbBlocks >= cbRequired)
+ {
+ return;
+ }
+ }
+}
+
+static void hgsmiMAMergeFreeBlocks(HGSMIMADATA *pMA, HGSMIOFFSET order)
+{
+ /* Try to create a free block with the order from smaller free blocks. */
+ if (order == 0)
+ {
+ /* No smaller blocks. */
+ return;
+ }
+
+ HGSMISIZE cbRequired = HGSMIMAOrder2Size(order);
+
+ /* Scan all free lists of smaller blocks.
+ *
+ * Get the sequence of free blocks before and after each free block.
+ * If possible, re-split the sequence to get the required block and other free block(s).
+ * If not possible, try the next free block.
+ *
+ * Free blocks are scanned from i to 0 orders.
+ */
+ HGSMIOFFSET i = order - 1;
+ for (;;)
+ {
+ HGSMIMABLOCK *pIter;
+ RTListForEach(&pMA->aListFreeBlocks[i], pIter, HGSMIMABLOCK, nodeFree)
+ {
+ HGSMI_ASSERT(HGSMI_MA_DESC_ORDER(pIter->descriptor) == i);
+
+ HGSMISIZE cbBlocks;
+ HGSMIMABLOCK *pFreeStart;
+ HGSMIMABLOCK *pFreeEnd;
+ hgsmiMAQueryFreeRange(pMA, pIter, cbRequired, &pFreeStart, &pFreeEnd, &cbBlocks);
+
+ HGSMI_ASSERT((cbBlocks / HGSMI_MA_BLOCK_SIZE_MIN) * HGSMI_MA_BLOCK_SIZE_MIN == cbBlocks);
+
+ /* Verify whether cbBlocks is enough for the requested block. */
+ if (cbBlocks >= cbRequired)
+ {
+ /* Build new free blocks starting from the requested. */
+ hgsmiMAReformatFreeBlocks(pMA, order, pFreeStart, pFreeEnd, cbBlocks);
+ i = 0; /* Leave the loop. */
+ break;
+ }
+ }
+
+ if (i == 0)
+ {
+ break;
+ }
+
+ --i;
+ }
+}
+
+static HGSMIOFFSET hgsmiMAAlloc(HGSMIMADATA *pMA, HGSMISIZE cb)
+{
+ if (cb > pMA->cbMaxBlock)
+ {
+ return HGSMIOFFSET_VOID;
+ }
+
+ if (cb < HGSMI_MA_BLOCK_SIZE_MIN)
+ {
+ cb = HGSMI_MA_BLOCK_SIZE_MIN;
+ }
+
+ HGSMIOFFSET order = HGSMIPopCnt32(cb - 1) - HGSMI_MA_DESC_ORDER_BASE;
+
+ HGSMI_ASSERT_RETURN(HGSMIMAOrder2Size(order) >= cb, HGSMIOFFSET_VOID);
+ HGSMI_ASSERT_RETURN(order < RT_ELEMENTS(pMA->aListFreeBlocks), HGSMIOFFSET_VOID);
+
+ HGSMIMABLOCK *pBlock = hgsmiMAGetFreeBlock(pMA, order);
+ if (RT_UNLIKELY(pBlock == NULL))
+ {
+ /* No free block with large enough size. Merge smaller free blocks and try again. */
+ hgsmiMAMergeFreeBlocks(pMA, order);
+ pBlock = hgsmiMAGetFreeBlock(pMA, order);
+ }
+
+ if (RT_LIKELY(pBlock != NULL))
+ {
+ RTListNodeRemove(&pBlock->nodeFree);
+ pBlock->descriptor &= ~HGSMI_MA_DESC_FREE_MASK;
+ return HGSMI_MA_DESC_OFFSET(pBlock->descriptor);
+ }
+
+ return HGSMIOFFSET_VOID;
+}
+
+static void hgsmiMAFree(HGSMIMADATA *pMA, HGSMIOFFSET off)
+{
+ if (off == HGSMIOFFSET_VOID)
+ {
+ return;
+ }
+
+ /* Find the block corresponding to the offset. */
+ HGSMI_ASSERT((off / HGSMI_MA_BLOCK_SIZE_MIN) * HGSMI_MA_BLOCK_SIZE_MIN == off);
+
+ HGSMIMABLOCK *pBlock = HGSMIMASearchOffset(pMA, off);
+ if (pBlock)
+ {
+ if (HGSMI_MA_DESC_OFFSET(pBlock->descriptor) == off)
+ {
+ /* Found the right block, mark it as free. */
+ pBlock->descriptor |= HGSMI_MA_DESC_FREE_MASK;
+ RTListAppend(&pMA->aListFreeBlocks[HGSMI_MA_DESC_ORDER(pBlock->descriptor)], &pBlock->nodeFree);
+ return;
+ }
+ }
+
+ HGSMI_ASSERT_FAILED();
+}
+
+int HGSMIMAInit(HGSMIMADATA *pMA, const HGSMIAREA *pArea,
+ HGSMIOFFSET *paDescriptors, uint32_t cDescriptors, HGSMISIZE cbMaxBlock,
+ const HGSMIENV *pEnv)
+{
+ HGSMI_ASSERT_RETURN(pArea->cbArea < UINT32_C(0x80000000), VERR_INVALID_PARAMETER);
+ HGSMI_ASSERT_RETURN(pArea->cbArea >= HGSMI_MA_BLOCK_SIZE_MIN, VERR_INVALID_PARAMETER);
+
+ RT_ZERO(*pMA);
+
+ HGSMISIZE cb = (pArea->cbArea / HGSMI_MA_BLOCK_SIZE_MIN) * HGSMI_MA_BLOCK_SIZE_MIN;
+
+ int rc = HGSMIAreaInitialize(&pMA->area, pArea->pu8Base, cb, 0);
+ if (RT_SUCCESS(rc))
+ {
+ pMA->env = *pEnv;
+
+ uint32_t i;
+ for (i = 0; i < RT_ELEMENTS(pMA->aListFreeBlocks); ++i)
+ {
+ RTListInit(&pMA->aListFreeBlocks[i]);
+ }
+ RTListInit(&pMA->listBlocks);
+
+ if (cDescriptors)
+ {
+ rc = hgsmiMARestore(pMA, paDescriptors, cDescriptors, cbMaxBlock);
+ }
+ else
+ {
+ rc = hgsmiMAFormat(pMA);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = hgsmiMARebuildFreeLists(pMA);
+ }
+ }
+
+ return rc;
+}
+
+void HGSMIMAUninit(HGSMIMADATA *pMA)
+{
+ HGSMIMABLOCK *pIter;
+ HGSMIMABLOCK *pNext;
+ /* If it has been initialized. */
+ if (pMA->listBlocks.pNext)
+ {
+ RTListForEachSafe(&pMA->listBlocks, pIter, pNext, HGSMIMABLOCK, nodeBlock)
+ {
+ RTListNodeRemove(&pIter->nodeBlock);
+ hgsmiMABlockFree(pMA, pIter);
+ }
+ }
+
+ RT_ZERO(*pMA);
+}
+
+HGSMIOFFSET HGSMIMAPointerToOffset(const HGSMIMADATA *pMA, const void RT_UNTRUSTED_VOLATILE_GUEST *pv)
+{
+ uintptr_t off = (uintptr_t)pv - (uintptr_t)pMA->area.pu8Base;
+ if (off < pMA->area.cbArea)
+ return pMA->area.offBase + off;
+
+ HGSMI_ASSERT_FAILED();
+ return HGSMIOFFSET_VOID;
+}
+
+static void RT_UNTRUSTED_VOLATILE_HSTGST *HGSMIMAOffsetToPointer(const HGSMIMADATA *pMA, HGSMIOFFSET off)
+{
+ if (HGSMIAreaContainsOffset(&pMA->area, off))
+ {
+ return HGSMIOffsetToPointer(&pMA->area, off);
+ }
+
+ HGSMI_ASSERT_FAILED();
+ return NULL;
+}
+
+void RT_UNTRUSTED_VOLATILE_HSTGST *HGSMIMAAlloc(HGSMIMADATA *pMA, HGSMISIZE cb)
+{
+ HGSMIOFFSET off = hgsmiMAAlloc(pMA, cb);
+ return HGSMIMAOffsetToPointer(pMA, off);
+}
+
+void HGSMIMAFree(HGSMIMADATA *pMA, void RT_UNTRUSTED_VOLATILE_GUEST *pv)
+{
+ HGSMIOFFSET off = HGSMIMAPointerToOffset(pMA, pv);
+ if (off != HGSMIOFFSET_VOID)
+ {
+ hgsmiMAFree(pMA, off);
+ }
+ else
+ {
+ HGSMI_ASSERT_FAILED();
+ }
+}
+
+HGSMIMABLOCK *HGSMIMASearchOffset(HGSMIMADATA *pMA, HGSMIOFFSET off)
+{
+ /* Binary search in the block list for the offset. */
+ HGSMIMABLOCK *pStart = RTListGetFirst(&pMA->listBlocks, HGSMIMABLOCK, nodeBlock);
+ HGSMIMABLOCK *pEnd = RTListGetLast(&pMA->listBlocks, HGSMIMABLOCK, nodeBlock);
+ HGSMIMABLOCK *pMiddle;
+
+ uint32_t iStart = 0;
+ uint32_t iEnd = pMA->cBlocks;
+ uint32_t iMiddle;
+
+ for (;;)
+ {
+ pMiddle = pStart;
+ iMiddle = iStart + (iEnd - iStart) / 2;
+ if (iMiddle == iStart)
+ {
+ break;
+ }
+
+ /* Find the block with the iMiddle index. Never go further than pEnd. */
+ uint32_t i;
+ for (i = iStart; i < iMiddle && pMiddle != pEnd; ++i)
+ {
+ pMiddle = RTListNodeGetNext(&pMiddle->nodeBlock, HGSMIMABLOCK, nodeBlock);
+ }
+
+ HGSMIOFFSET offMiddle = HGSMI_MA_DESC_OFFSET(pMiddle->descriptor);
+ if (offMiddle > off)
+ {
+ pEnd = pMiddle;
+ iEnd = iMiddle;
+ }
+ else
+ {
+ pStart = pMiddle;
+ iStart = iMiddle;
+ }
+ }
+
+ return pMiddle;
+}
+
+
+/*
+ * Helper.
+ */
+
+uint32_t HGSMIPopCnt32(uint32_t u32)
+{
+ uint32_t c = 0;
+ if (u32 > 0xFFFF) { c += 16; u32 >>= 16; }
+ if (u32 > 0xFF) { c += 8; u32 >>= 8; }
+ if (u32 > 0xF) { c += 4; u32 >>= 4; }
+ if (u32 > 0x3) { c += 2; u32 >>= 2; }
+ if (u32 > 0x1) { c += 1; u32 >>= 1; }
+ return c + u32;
+}
diff --git a/src/VBox/GuestHost/HGSMI/Makefile.kmk b/src/VBox/GuestHost/HGSMI/Makefile.kmk
new file mode 100644
index 00000000..3f2c17b8
--- /dev/null
+++ b/src/VBox/GuestHost/HGSMI/Makefile.kmk
@@ -0,0 +1,59 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the common HGSMI library.
+#
+
+#
+# Copyright (C) 2006-2022 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
+
+
+
+
+#
+# HGSMIGuestR0Lib - For guest additions drivers.
+#
+ifdef VBOX_WITH_ADDITION_DRIVERS
+LIBRARIES += HGSMIGuestR0Lib
+HGSMIGuestR0Lib_TEMPLATE = VBOXGUESTR0LIB
+HGSMIGuestR0Lib_DEFS =
+HGSMIGuestR0Lib_INCS = $(VBOX_GRAPHICS_INCS)
+HGSMIGuestR0Lib_SOURCES = \
+ HGSMICommon.cpp \
+ HGSMIMemAlloc.cpp
+endif
+
+#
+# HGSMIHostLib - For host devices (R3 only).
+#
+LIBRARIES += HGSMIHostR3Lib
+HGSMIHostR3Lib_TEMPLATE = VBOXR3
+HGSMIHostR3Lib_DEFS =
+HGSMIHostR3Lib_INCS = $(VBOX_GRAPHICS_INCS)
+HGSMIHostR3Lib_SOURCES = \
+ HGSMICommon.cpp \
+ HGSMIMemAlloc.cpp
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/GuestHost/Makefile.kmk b/src/VBox/GuestHost/Makefile.kmk
new file mode 100644
index 00000000..652bb858
--- /dev/null
+++ b/src/VBox/GuestHost/Makefile.kmk
@@ -0,0 +1,45 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the VirtualBox Guest/Host part
+#
+
+#
+# Copyright (C) 2008-2022 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).
+ifdef VBOX_WITH_HGSMI
+ include $(PATH_SUB_CURRENT)/HGSMI/Makefile.kmk
+endif
+
+ifdef VBOX_WITH_DRAG_AND_DROP
+ include $(PATH_SUB_CURRENT)/DragAndDrop/Makefile.kmk
+endif
+
+ifdef VBOX_WITH_HGCM
+ include $(PATH_SUB_CURRENT)/SharedClipboard/Makefile.kmk
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp
new file mode 100644
index 00000000..fc462109
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp
@@ -0,0 +1,978 @@
+/* $Id: ClipboardDataObjectImpl-win.cpp $ */
+/** @file
+ * ClipboardDataObjectImpl-win.cpp - Shared Clipboard IDataObject implementation.
+ */
+
+/*
+ * Copyright (C) 2019-2022 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/GuestHost/SharedClipboard-win.h>
+#include <VBox/GuestHost/SharedClipboard-transfers.h>
+
+#include <iprt/win/windows.h>
+#include <iprt/win/shlobj.h>
+#include <iprt/win/shlwapi.h>
+
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+#include <iprt/uri.h>
+#include <iprt/utf16.h>
+
+#include <iprt/errcore.h>
+#include <VBox/log.h>
+
+/** @todo Also handle Unicode entries.
+ * !!! WARNING: Buggy, doesn't work yet (some memory corruption / garbage in the file name descriptions) !!! */
+//#define VBOX_CLIPBOARD_WITH_UNICODE_SUPPORT 1
+
+SharedClipboardWinDataObject::SharedClipboardWinDataObject(PSHCLTRANSFER pTransfer,
+ LPFORMATETC pFormatEtc, LPSTGMEDIUM pStgMed, ULONG cFormats)
+ : m_enmStatus(Uninitialized)
+ , m_lRefCount(0)
+ , m_cFormats(0)
+ , m_pTransfer(pTransfer)
+ , m_pStream(NULL)
+ , m_uObjIdx(0)
+ , m_fRunning(false)
+ , m_EventListComplete(NIL_RTSEMEVENT)
+ , m_EventTransferComplete(NIL_RTSEMEVENT)
+{
+ AssertPtr(m_pTransfer);
+
+ HRESULT hr;
+
+ ULONG cFixedFormats = 3; /* CFSTR_FILEDESCRIPTORA + CFSTR_FILECONTENTS + CFSTR_PERFORMEDDROPEFFECT */
+#ifdef VBOX_CLIPBOARD_WITH_UNICODE_SUPPORT
+ cFixedFormats++; /* CFSTR_FILEDESCRIPTORW */
+#endif
+ const ULONG cAllFormats = cFormats + cFixedFormats;
+
+ try
+ {
+ m_pFormatEtc = new FORMATETC[cAllFormats];
+ RT_BZERO(m_pFormatEtc, sizeof(FORMATETC) * cAllFormats);
+ m_pStgMedium = new STGMEDIUM[cAllFormats];
+ RT_BZERO(m_pStgMedium, sizeof(STGMEDIUM) * cAllFormats);
+
+ /** @todo Do we need CFSTR_FILENAME / CFSTR_SHELLIDLIST here? */
+
+ /*
+ * Register fixed formats.
+ */
+ unsigned uIdx = 0;
+
+ LogFlowFunc(("Registering CFSTR_FILEDESCRIPTORA ...\n"));
+ m_cfFileDescriptorA = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
+ registerFormat(&m_pFormatEtc[uIdx++], m_cfFileDescriptorA);
+#ifdef VBOX_CLIPBOARD_WITH_UNICODE_SUPPORT
+ LogFlowFunc(("Registering CFSTR_FILEDESCRIPTORW ...\n"));
+ m_cfFileDescriptorW = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
+ registerFormat(&m_pFormatEtc[uIdx++], m_cfFileDescriptorW);
+#endif
+
+ /* IStream interface, implemented in ClipboardStreamImpl-win.cpp. */
+ LogFlowFunc(("Registering CFSTR_FILECONTENTS ...\n"));
+ m_cfFileContents = RegisterClipboardFormat(CFSTR_FILECONTENTS);
+ registerFormat(&m_pFormatEtc[uIdx++], m_cfFileContents, TYMED_ISTREAM, 0 /* lIndex */);
+
+ /* We want to know from the target what the outcome of the operation was to react accordingly (e.g. abort a transfer). */
+ LogFlowFunc(("Registering CFSTR_PERFORMEDDROPEFFECT ...\n"));
+ m_cfPerformedDropEffect = RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT);
+ registerFormat(&m_pFormatEtc[uIdx++], m_cfPerformedDropEffect, TYMED_HGLOBAL, -1 /* lIndex */, DVASPECT_CONTENT);
+
+ /*
+ * Registration of dynamic formats needed?
+ */
+ LogFlowFunc(("%RU32 dynamic formats\n", cFormats));
+ if (cFormats)
+ {
+ AssertPtr(pFormatEtc);
+ AssertPtr(pStgMed);
+
+ for (ULONG i = 0; i < cFormats; i++)
+ {
+ LogFlowFunc(("Format %RU32: cfFormat=%RI16, tyMed=%RU32, dwAspect=%RU32\n",
+ i, pFormatEtc[i].cfFormat, pFormatEtc[i].tymed, pFormatEtc[i].dwAspect));
+ m_pFormatEtc[cFixedFormats + i] = pFormatEtc[i];
+ m_pStgMedium[cFixedFormats + i] = pStgMed[i];
+ }
+ }
+
+ hr = S_OK;
+ }
+ catch (std::bad_alloc &)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ m_cFormats = cAllFormats;
+ m_enmStatus = Initialized;
+
+ int rc2 = RTSemEventCreate(&m_EventListComplete);
+ AssertRC(rc2);
+ rc2 = RTSemEventCreate(&m_EventTransferComplete);
+ AssertRC(rc2);
+ }
+
+ LogFlowFunc(("cAllFormats=%RU32, hr=%Rhrc\n", cAllFormats, hr));
+}
+
+SharedClipboardWinDataObject::~SharedClipboardWinDataObject(void)
+{
+ LogFlowFuncEnter();
+
+ RTSemEventDestroy(m_EventListComplete);
+ m_EventListComplete = NIL_RTSEMEVENT;
+
+ RTSemEventDestroy(m_EventTransferComplete);
+ m_EventTransferComplete = NIL_RTSEMEVENT;
+
+ if (m_pStream)
+ m_pStream->Release();
+
+ if (m_pFormatEtc)
+ delete[] m_pFormatEtc;
+
+ if (m_pStgMedium)
+ delete[] m_pStgMedium;
+
+ LogFlowFunc(("mRefCount=%RI32\n", m_lRefCount));
+}
+
+/*
+ * IUnknown methods.
+ */
+
+STDMETHODIMP_(ULONG) SharedClipboardWinDataObject::AddRef(void)
+{
+ LONG lCount = InterlockedIncrement(&m_lRefCount);
+ LogFlowFunc(("lCount=%RI32\n", lCount));
+ return lCount;
+}
+
+STDMETHODIMP_(ULONG) SharedClipboardWinDataObject::Release(void)
+{
+ LONG lCount = InterlockedDecrement(&m_lRefCount);
+ LogFlowFunc(("lCount=%RI32\n", m_lRefCount));
+ if (lCount == 0)
+ {
+ delete this;
+ return 0;
+ }
+
+ return lCount;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::QueryInterface(REFIID iid, void **ppvObject)
+{
+ AssertPtrReturn(ppvObject, E_INVALIDARG);
+
+ if ( iid == IID_IDataObject
+ || iid == IID_IUnknown)
+ {
+ AddRef();
+ *ppvObject = this;
+ return S_OK;
+ }
+
+ *ppvObject = 0;
+ return E_NOINTERFACE;
+}
+
+/**
+ * Copies a chunk of data into a HGLOBAL object.
+ *
+ * @returns VBox status code.
+ * @param pvData Data to copy.
+ * @param cbData Size (in bytes) to copy.
+ * @param fFlags GlobalAlloc flags, used for allocating the HGLOBAL block.
+ * @param phGlobal Where to store the allocated HGLOBAL object.
+ */
+int SharedClipboardWinDataObject::copyToHGlobal(const void *pvData, size_t cbData, UINT fFlags, HGLOBAL *phGlobal)
+{
+ AssertPtrReturn(phGlobal, VERR_INVALID_POINTER);
+
+ HGLOBAL hGlobal = GlobalAlloc(fFlags, cbData);
+ if (!hGlobal)
+ return VERR_NO_MEMORY;
+
+ void *pvAlloc = GlobalLock(hGlobal);
+ if (pvAlloc)
+ {
+ CopyMemory(pvAlloc, pvData, cbData);
+ GlobalUnlock(hGlobal);
+
+ *phGlobal = hGlobal;
+
+ return VINF_SUCCESS;
+ }
+
+ GlobalFree(hGlobal);
+ return VERR_ACCESS_DENIED;
+}
+
+/**
+ * Reads (handles) a specific directory reursively and inserts its entry into the
+ * objects's entry list.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Shared Clipboard transfer object to handle.
+ * @param strDir Directory path to handle.
+ */
+int SharedClipboardWinDataObject::readDir(PSHCLTRANSFER pTransfer, const Utf8Str &strDir)
+{
+ LogFlowFunc(("strDir=%s\n", strDir.c_str()));
+
+ SHCLLISTOPENPARMS openParmsList;
+ int rc = ShClTransferListOpenParmsInit(&openParmsList);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrCopy(openParmsList.pszPath, openParmsList.cbPath, strDir.c_str());
+ if (RT_SUCCESS(rc))
+ {
+ SHCLLISTHANDLE hList;
+ rc = ShClTransferListOpen(pTransfer, &openParmsList, &hList);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("strDir=%s -> hList=%RU64\n", strDir.c_str(), hList));
+
+ SHCLLISTHDR hdrList;
+ rc = ShClTransferListGetHeader(pTransfer, hList, &hdrList);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("cTotalObjects=%RU64, cbTotalSize=%RU64\n\n",
+ hdrList.cTotalObjects, hdrList.cbTotalSize));
+
+ for (uint64_t o = 0; o < hdrList.cTotalObjects; o++)
+ {
+ SHCLLISTENTRY entryList;
+ rc = ShClTransferListEntryInit(&entryList);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ShClTransferListRead(pTransfer, hList, &entryList);
+ if (RT_SUCCESS(rc))
+ {
+ if (ShClTransferListEntryIsValid(&entryList))
+ {
+ PSHCLFSOBJINFO pFsObjInfo = (PSHCLFSOBJINFO)entryList.pvInfo;
+ Assert(entryList.cbInfo == sizeof(SHCLFSOBJINFO));
+
+ Utf8Str strPath = strDir + Utf8Str("\\") + Utf8Str(entryList.pszName);
+
+ LogFlowFunc(("\t%s (%RU64 bytes) -> %s\n",
+ entryList.pszName, pFsObjInfo->cbObject, strPath.c_str()));
+
+ if (RTFS_IS_DIRECTORY(pFsObjInfo->Attr.fMode))
+ {
+ FSOBJENTRY objEntry = { strPath.c_str(), *pFsObjInfo };
+
+ m_lstEntries.push_back(objEntry); /** @todo Can this throw? */
+
+ rc = readDir(pTransfer, strPath.c_str());
+ }
+ else if (RTFS_IS_FILE(pFsObjInfo->Attr.fMode))
+ {
+ FSOBJENTRY objEntry = { strPath.c_str(), *pFsObjInfo };
+
+ m_lstEntries.push_back(objEntry); /** @todo Can this throw? */
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ /** @todo Handle symlinks. */
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ ShClTransferListEntryDestroy(&entryList);
+ }
+
+ if ( RT_FAILURE(rc)
+ && pTransfer->Thread.fStop)
+ break;
+ }
+ }
+
+ ShClTransferListClose(pTransfer, hList);
+ }
+ }
+
+ ShClTransferListOpenParmsDestroy(&openParmsList);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Thread for reading transfer data.
+ * The data object needs the (high level, root) transfer listing at the time of ::GetData(), so we need
+ * to block and wait until we have this data (via this thread) and continue.
+ *
+ * @returns VBox status code.
+ * @param ThreadSelf Thread handle. Unused at the moment.
+ * @param pvUser Pointer to user-provided data. Of type SharedClipboardWinDataObject.
+ */
+/* static */
+DECLCALLBACK(int) SharedClipboardWinDataObject::readThread(RTTHREAD ThreadSelf, void *pvUser)
+{
+ RT_NOREF(ThreadSelf);
+
+ LogFlowFuncEnter();
+
+ SharedClipboardWinDataObject *pThis = (SharedClipboardWinDataObject *)pvUser;
+
+ PSHCLTRANSFER pTransfer = pThis->m_pTransfer;
+ AssertPtr(pTransfer);
+
+ pTransfer->Thread.fStarted = true;
+ pTransfer->Thread.fStop = false;
+
+ RTThreadUserSignal(RTThreadSelf());
+
+ LogRel2(("Shared Clipboard: Calculating transfer ...\n"));
+
+ PSHCLROOTLIST pRootList;
+ int rc = ShClTransferRootsGet(pTransfer, &pRootList);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("cRoots=%RU32\n\n", pRootList->Hdr.cRoots));
+
+ for (uint32_t i = 0; i < pRootList->Hdr.cRoots; i++)
+ {
+ PSHCLLISTENTRY pRootEntry = &pRootList->paEntries[i];
+ AssertPtr(pRootEntry);
+
+ Assert(pRootEntry->cbInfo == sizeof(SHCLFSOBJINFO));
+ PSHCLFSOBJINFO pFsObjInfo = (PSHCLFSOBJINFO)pRootEntry->pvInfo;
+
+ LogFlowFunc(("pszRoot=%s, fMode=0x%x\n", pRootEntry->pszName, pFsObjInfo->Attr.fMode));
+
+ if (RTFS_IS_DIRECTORY(pFsObjInfo->Attr.fMode))
+ {
+ FSOBJENTRY objEntry = { pRootEntry->pszName, *pFsObjInfo };
+
+ pThis->m_lstEntries.push_back(objEntry); /** @todo Can this throw? */
+
+ rc = pThis->readDir(pTransfer, pRootEntry->pszName);
+ }
+ else if (RTFS_IS_FILE(pFsObjInfo->Attr.fMode))
+ {
+ FSOBJENTRY objEntry = { pRootEntry->pszName, *pFsObjInfo };
+
+ pThis->m_lstEntries.push_back(objEntry); /** @todo Can this throw? */
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ if (ASMAtomicReadBool(&pTransfer->Thread.fStop))
+ {
+ LogRel2(("Shared Clipboard: Stopping transfer calculation ...\n"));
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ ShClTransferRootListFree(pRootList);
+ pRootList = NULL;
+
+ if ( RT_SUCCESS(rc)
+ && !ASMAtomicReadBool(&pTransfer->Thread.fStop))
+ {
+ LogRel2(("Shared Clipboard: Transfer calculation complete (%zu root entries)\n", pThis->m_lstEntries.size()));
+
+ /*
+ * Signal the "list complete" event so that this data object can return (valid) data via ::GetData().
+ * This in turn then will create IStream instances (by the OS) for each file system object to handle.
+ */
+ int rc2 = RTSemEventSignal(pThis->m_EventListComplete);
+ AssertRC(rc2);
+
+ if (pThis->m_lstEntries.size())
+ {
+ LogRel2(("Shared Clipboard: Waiting for transfer to complete ...\n"));
+
+ LogFlowFunc(("Waiting for transfer to complete ...\n"));
+
+ /* Transferring stuff can take a while, so don't use any timeout here. */
+ rc2 = RTSemEventWait(pThis->m_EventTransferComplete, RT_INDEFINITE_WAIT);
+ AssertRC(rc2);
+
+ switch (pThis->m_enmStatus)
+ {
+ case Completed:
+ LogRel2(("Shared Clipboard: Transfer complete\n"));
+ break;
+
+ case Canceled:
+ LogRel2(("Shared Clipboard: Transfer canceled\n"));
+ break;
+
+ case Error:
+ LogRel2(("Shared Clipboard: Transfer error occurred\n"));
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ LogRel(("Shared Clipboard: No transfer root entries found -- should not happen, please file a bug report\n"));
+ }
+ else if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: Transfer failed with %Rrc\n", rc));
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Creates a FILEGROUPDESCRIPTOR object from a given Shared Clipboard transfer and stores the result into an HGLOBAL object.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Shared Clipboard transfer to create file grou desciprtor for.
+ * @param fUnicode Whether the FILEGROUPDESCRIPTOR object shall contain Unicode data or not.
+ * @param phGlobal Where to store the allocated HGLOBAL object on success.
+ */
+int SharedClipboardWinDataObject::createFileGroupDescriptorFromTransfer(PSHCLTRANSFER pTransfer,
+ bool fUnicode, HGLOBAL *phGlobal)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(phGlobal, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ const size_t cbFileGroupDescriptor = fUnicode ? sizeof(FILEGROUPDESCRIPTORW) : sizeof(FILEGROUPDESCRIPTORA);
+ const size_t cbFileDescriptor = fUnicode ? sizeof(FILEDESCRIPTORW) : sizeof(FILEDESCRIPTORA);
+
+ const UINT cItems = (UINT)m_lstEntries.size(); /** UINT vs. size_t. */
+ if (!cItems)
+ return VERR_NOT_FOUND;
+
+ UINT curIdx = 0; /* Current index of the handled file group descriptor (FGD). */
+
+ const size_t cbFGD = cbFileGroupDescriptor + (cbFileDescriptor * (cItems - 1));
+
+ LogFunc(("fUnicode=%RTbool, cItems=%u, cbFileDescriptor=%zu\n", fUnicode, cItems, cbFileDescriptor));
+
+ /* FILEGROUPDESCRIPTORA / FILEGROUPDESCRIPTOR matches except the cFileName member (TCHAR vs. WCHAR). */
+ FILEGROUPDESCRIPTOR *pFGD = (FILEGROUPDESCRIPTOR *)RTMemAllocZ(cbFGD);
+ if (!pFGD)
+ return VERR_NO_MEMORY;
+
+ int rc = VINF_SUCCESS;
+
+ pFGD->cItems = cItems;
+
+ char *pszFileSpec = NULL;
+
+ FsObjEntryList::const_iterator itRoot = m_lstEntries.begin();
+ while (itRoot != m_lstEntries.end())
+ {
+ FILEDESCRIPTOR *pFD = &pFGD->fgd[curIdx];
+ RT_BZERO(pFD, cbFileDescriptor);
+
+ const char *pszFile = itRoot->strPath.c_str();
+ AssertPtr(pszFile);
+
+ pszFileSpec = RTStrDup(pszFile);
+ AssertBreakStmt(pszFileSpec != NULL, rc = VERR_NO_MEMORY);
+
+ if (fUnicode)
+ {
+ PRTUTF16 pwszFileSpec;
+ rc = RTStrToUtf16(pszFileSpec, &pwszFileSpec);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTUtf16CopyEx((PRTUTF16 )pFD->cFileName, sizeof(pFD->cFileName) / sizeof(WCHAR),
+ pwszFileSpec, RTUtf16Len(pwszFileSpec));
+ RTUtf16Free(pwszFileSpec);
+
+ LogFlowFunc(("pFD->cFileNameW=%ls\n", pFD->cFileName));
+ }
+ }
+ else
+ {
+ rc = RTStrCopy(pFD->cFileName, sizeof(pFD->cFileName), pszFileSpec);
+ LogFlowFunc(("pFD->cFileNameA=%s\n", pFD->cFileName));
+ }
+
+ RTStrFree(pszFileSpec);
+ pszFileSpec = NULL;
+
+ if (RT_FAILURE(rc))
+ break;
+
+ pFD->dwFlags = FD_PROGRESSUI | FD_ATTRIBUTES;
+ if (fUnicode) /** @todo Only >= Vista. */
+ pFD->dwFlags |= FD_UNICODE;
+ pFD->dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
+
+ const SHCLFSOBJINFO *pObjInfo = &itRoot->objInfo;
+
+ if (RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode))
+ {
+ pFD->dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
+ }
+ else if (RTFS_IS_FILE(pObjInfo->Attr.fMode))
+ {
+ pFD->dwFlags |= FD_FILESIZE;
+
+ const uint64_t cbObjSize = pObjInfo->cbObject;
+
+ pFD->nFileSizeHigh = RT_HI_U32(cbObjSize);
+ pFD->nFileSizeLow = RT_LO_U32(cbObjSize);
+ }
+ else if (RTFS_IS_SYMLINK(pObjInfo->Attr.fMode))
+ {
+ /** @todo Implement. */
+ }
+#if 0 /** @todo Implement this. */
+ pFD->dwFlags = FD_ATTRIBUTES | FD_CREATETIME | FD_ACCESSTIME | FD_WRITESTIME | FD_FILESIZE;
+ pFD->dwFileAttributes =
+ pFD->ftCreationTime =
+ pFD->ftLastAccessTime =
+ pFD->ftLastWriteTime =
+#endif
+ ++curIdx;
+ ++itRoot;
+ }
+
+ if (pszFileSpec)
+ RTStrFree(pszFileSpec);
+
+ if (RT_SUCCESS(rc))
+ rc = copyToHGlobal(pFGD, cbFGD, GMEM_MOVEABLE, phGlobal);
+
+ RTMemFree(pFGD);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Retrieves the data stored in this object and store the result in
+ * pMedium.
+ *
+ * @return IPRT status code.
+ * @return HRESULT
+ * @param pFormatEtc
+ * @param pMedium
+ */
+STDMETHODIMP SharedClipboardWinDataObject::GetData(LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium)
+{
+ AssertPtrReturn(pFormatEtc, DV_E_FORMATETC);
+ AssertPtrReturn(pMedium, DV_E_FORMATETC);
+
+ LogFlowFuncEnter();
+
+ LogFlowFunc(("lIndex=%RI32\n", pFormatEtc->lindex));
+
+ /*
+ * Initialize default values.
+ */
+ RT_BZERO(pMedium, sizeof(STGMEDIUM));
+
+ HRESULT hr = DV_E_FORMATETC; /* Play safe. */
+
+ if ( pFormatEtc->cfFormat == m_cfFileDescriptorA
+#ifdef VBOX_CLIPBOARD_WITH_UNICODE_SUPPORT
+ || pFormatEtc->cfFormat == m_cfFileDescriptorW
+#endif
+ )
+ {
+ const bool fUnicode = pFormatEtc->cfFormat == m_cfFileDescriptorW;
+
+ const uint32_t enmTransferStatus = ShClTransferGetStatus(m_pTransfer);
+ RT_NOREF(enmTransferStatus);
+
+ LogFlowFunc(("FormatIndex_FileDescriptor%s, enmTransferStatus=%s, m_fRunning=%RTbool\n",
+ fUnicode ? "W" : "A", ShClTransferStatusToStr(enmTransferStatus), m_fRunning));
+
+ int rc;
+
+ /* The caller can call GetData() several times, so make sure we don't do the same transfer multiple times. */
+ if (!m_fRunning)
+ {
+ /* Start the transfer asynchronously in a separate thread. */
+ rc = ShClTransferRun(m_pTransfer, &SharedClipboardWinDataObject::readThread, this);
+ if (RT_SUCCESS(rc))
+ {
+ m_fRunning = true;
+
+ /* Don't block for too long here, as this also will screw other apps running on the OS. */
+ LogFunc(("Waiting for listing to arrive ...\n"));
+ rc = RTSemEventWait(m_EventListComplete, 30 * 1000 /* 30s timeout */);
+ if (RT_SUCCESS(rc))
+ {
+ LogFunc(("Listing complete\n"));
+ }
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ HGLOBAL hGlobal;
+ rc = createFileGroupDescriptorFromTransfer(m_pTransfer, fUnicode, &hGlobal);
+ if (RT_SUCCESS(rc))
+ {
+ pMedium->tymed = TYMED_HGLOBAL;
+ pMedium->hGlobal = hGlobal;
+ /* Note: hGlobal now is being owned by pMedium / the caller. */
+
+ hr = S_OK;
+ }
+ else /* We can't tell any better to the caller, unfortunately. */
+ hr = E_UNEXPECTED;
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: Data object unable to get data, rc=%Rrc\n", rc));
+ }
+
+ if (pFormatEtc->cfFormat == m_cfFileContents)
+ {
+ if ( pFormatEtc->lindex >= 0
+ && (ULONG)pFormatEtc->lindex < m_lstEntries.size())
+ {
+ m_uObjIdx = pFormatEtc->lindex; /* lIndex of FormatEtc contains the actual index to the object being handled. */
+
+ FSOBJENTRY &fsObjEntry = m_lstEntries.at(m_uObjIdx);
+
+ LogFlowFunc(("FormatIndex_FileContents: m_uObjIdx=%u (entry '%s')\n", m_uObjIdx, fsObjEntry.strPath.c_str()));
+
+ LogRel2(("Shared Clipboard: Receiving object '%s' ...\n", fsObjEntry.strPath.c_str()));
+
+ /* Hand-in the provider so that our IStream implementation can continue working with it. */
+ hr = SharedClipboardWinStreamImpl::Create(this /* pParent */, m_pTransfer,
+ fsObjEntry.strPath.c_str()/* File name */, &fsObjEntry.objInfo /* PSHCLFSOBJINFO */,
+ &m_pStream);
+ if (SUCCEEDED(hr))
+ {
+ /* Hand over the stream to the caller. */
+ pMedium->tymed = TYMED_ISTREAM;
+ pMedium->pstm = m_pStream;
+ }
+ }
+ }
+ else if (pFormatEtc->cfFormat == m_cfPerformedDropEffect)
+ {
+ HGLOBAL hGlobal = GlobalAlloc(GHND, sizeof(DWORD));
+
+ DWORD* pdwDropEffect = (DWORD*)GlobalLock(hGlobal);
+ *pdwDropEffect = DROPEFFECT_COPY;
+
+ GlobalUnlock(hGlobal);
+
+ pMedium->tymed = TYMED_HGLOBAL;
+ pMedium->hGlobal = hGlobal;
+ pMedium->pUnkForRelease = NULL;
+ }
+
+ if ( FAILED(hr)
+ && hr != DV_E_FORMATETC) /* Can happen if the caller queries unknown / unhandled formats. */
+ {
+ LogRel(("Shared Clipboard: Error returning data from data object (%Rhrc)\n", hr));
+ }
+
+ LogFlowFunc(("hr=%Rhrc\n", hr));
+ return hr;
+}
+
+/**
+ * Only required for IStream / IStorage interfaces.
+ *
+ * @return IPRT status code.
+ * @return HRESULT
+ * @param pFormatEtc
+ * @param pMedium
+ */
+STDMETHODIMP SharedClipboardWinDataObject::GetDataHere(LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium)
+{
+ RT_NOREF(pFormatEtc, pMedium);
+ LogFlowFunc(("\n"));
+ return E_NOTIMPL;
+}
+
+/**
+ * Query if this objects supports a specific format.
+ *
+ * @return IPRT status code.
+ * @return HRESULT
+ * @param pFormatEtc
+ */
+STDMETHODIMP SharedClipboardWinDataObject::QueryGetData(LPFORMATETC pFormatEtc)
+{
+ LogFlowFunc(("\n"));
+ return lookupFormatEtc(pFormatEtc, NULL /* puIndex */) ? S_OK : DV_E_FORMATETC;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::GetCanonicalFormatEtc(LPFORMATETC pFormatEtc, LPFORMATETC pFormatEtcOut)
+{
+ RT_NOREF(pFormatEtc);
+ LogFlowFunc(("\n"));
+
+ /* Set this to NULL in any case. */
+ pFormatEtcOut->ptd = NULL;
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::SetData(LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium, BOOL fRelease)
+{
+ if ( pFormatEtc == NULL
+ || pMedium == NULL)
+ return E_INVALIDARG;
+
+ if (pFormatEtc->lindex != -1)
+ return DV_E_LINDEX;
+
+ if (pFormatEtc->tymed != TYMED_HGLOBAL)
+ return DV_E_TYMED;
+
+ if (pFormatEtc->dwAspect != DVASPECT_CONTENT)
+ return DV_E_DVASPECT;
+
+ LogFlowFunc(("cfFormat=%RU16, lookupFormatEtc=%RTbool\n",
+ pFormatEtc->cfFormat, lookupFormatEtc(pFormatEtc, NULL /* puIndex */)));
+
+ /* CFSTR_PERFORMEDDROPEFFECT is used by the drop target (caller of this IDataObject) to communicate
+ * the outcome of the overall operation. */
+ if ( pFormatEtc->cfFormat == m_cfPerformedDropEffect
+ && pMedium->tymed == TYMED_HGLOBAL)
+ {
+ DWORD dwEffect = *(DWORD *)GlobalLock(pMedium->hGlobal);
+ GlobalUnlock(pMedium->hGlobal);
+
+ LogFlowFunc(("dwEffect=%RI32\n", dwEffect));
+
+ /* Did the user cancel the operation via UI (shell)? This also might happen when overwriting an existing file
+ * and the user doesn't want to allow this. */
+ if (dwEffect == DROPEFFECT_NONE)
+ {
+ LogRel2(("Shared Clipboard: Transfer canceled by user interaction\n"));
+
+ OnTransferCanceled();
+ }
+ /** @todo Detect move / overwrite actions here. */
+
+ if (fRelease)
+ ReleaseStgMedium(pMedium);
+
+ return S_OK;
+ }
+
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppEnumFormatEtc)
+{
+ LogFlowFunc(("dwDirection=%RI32, mcFormats=%RI32, mpFormatEtc=%p\n", dwDirection, m_cFormats, m_pFormatEtc));
+
+ HRESULT hr;
+ if (dwDirection == DATADIR_GET)
+ hr = SharedClipboardWinEnumFormatEtc::CreateEnumFormatEtc(m_cFormats, m_pFormatEtc, ppEnumFormatEtc);
+ else
+ hr = E_NOTIMPL;
+
+ LogFlowFunc(("hr=%Rhrc\n", hr));
+ return hr;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::DAdvise(LPFORMATETC pFormatEtc, DWORD fAdvise, IAdviseSink *pAdvSink, DWORD *pdwConnection)
+{
+ RT_NOREF(pFormatEtc, fAdvise, pAdvSink, pdwConnection);
+ return OLE_E_ADVISENOTSUPPORTED;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::DUnadvise(DWORD dwConnection)
+{
+ RT_NOREF(dwConnection);
+ return OLE_E_ADVISENOTSUPPORTED;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::EnumDAdvise(IEnumSTATDATA **ppEnumAdvise)
+{
+ RT_NOREF(ppEnumAdvise);
+ return OLE_E_ADVISENOTSUPPORTED;
+}
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_WIN_ASYNC
+/*
+ * IDataObjectAsyncCapability methods.
+ */
+
+STDMETHODIMP SharedClipboardWinDataObject::EndOperation(HRESULT hResult, IBindCtx *pbcReserved, DWORD dwEffects)
+{
+ RT_NOREF(hResult, pbcReserved, dwEffects);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::GetAsyncMode(BOOL *pfIsOpAsync)
+{
+ RT_NOREF(pfIsOpAsync);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::InOperation(BOOL *pfInAsyncOp)
+{
+ RT_NOREF(pfInAsyncOp);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::SetAsyncMode(BOOL fDoOpAsync)
+{
+ RT_NOREF(fDoOpAsync);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::StartOperation(IBindCtx *pbcReserved)
+{
+ RT_NOREF(pbcReserved);
+ return E_NOTIMPL;
+}
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_WIN_ASYNC */
+
+/*
+ * Own stuff.
+ */
+
+int SharedClipboardWinDataObject::Init(void)
+{
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+void SharedClipboardWinDataObject::OnTransferComplete(int rc /* = VINF_SUCESS */)
+{
+ RT_NOREF(rc);
+
+ LogFlowFunc(("m_uObjIdx=%RU32 (total: %zu)\n", m_uObjIdx, m_lstEntries.size()));
+
+ if (RT_SUCCESS(rc))
+ {
+ const bool fComplete = m_uObjIdx == m_lstEntries.size() - 1 /* Object index is zero-based */;
+ if (fComplete)
+ {
+ m_enmStatus = Completed;
+ }
+ }
+ else
+ m_enmStatus = Error;
+
+ if (m_enmStatus != Initialized)
+ {
+ if (m_EventTransferComplete != NIL_RTSEMEVENT)
+ {
+ int rc2 = RTSemEventSignal(m_EventTransferComplete);
+ AssertRC(rc2);
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+}
+
+void SharedClipboardWinDataObject::OnTransferCanceled(void)
+{
+ LogFlowFuncEnter();
+
+ m_enmStatus = Canceled;
+
+ if (m_EventTransferComplete != NIL_RTSEMEVENT)
+ {
+ int rc2 = RTSemEventSignal(m_EventTransferComplete);
+ AssertRC(rc2);
+ }
+
+ LogFlowFuncLeave();
+}
+
+/* static */
+void SharedClipboardWinDataObject::logFormat(CLIPFORMAT fmt)
+{
+ char szFormat[128];
+ if (GetClipboardFormatName(fmt, szFormat, sizeof(szFormat)))
+ {
+ LogFlowFunc(("clipFormat=%RI16 -> %s\n", fmt, szFormat));
+ }
+ else
+ LogFlowFunc(("clipFormat=%RI16 is unknown\n", fmt));
+}
+
+bool SharedClipboardWinDataObject::lookupFormatEtc(LPFORMATETC pFormatEtc, ULONG *puIndex)
+{
+ AssertReturn(pFormatEtc, false);
+ /* puIndex is optional. */
+
+ for (ULONG i = 0; i < m_cFormats; i++)
+ {
+ if( (pFormatEtc->tymed & m_pFormatEtc[i].tymed)
+ && pFormatEtc->cfFormat == m_pFormatEtc[i].cfFormat)
+ /* Note: Do *not* compare dwAspect here, as this can be dynamic, depending on how the object should be represented. */
+ //&& pFormatEtc->dwAspect == m_pFormatEtc[i].dwAspect)
+ {
+ LogRel2(("Shared Clipboard: Format found: tyMed=%RI32, cfFormat=%RI16, dwAspect=%RI32, ulIndex=%RU32\n",
+ pFormatEtc->tymed, pFormatEtc->cfFormat, pFormatEtc->dwAspect, i));
+ if (puIndex)
+ *puIndex = i;
+ return true;
+ }
+ }
+
+ LogRel2(("Shared Clipboard: Format NOT found: tyMed=%RI32, cfFormat=%RI16, dwAspect=%RI32\n",
+ pFormatEtc->tymed, pFormatEtc->cfFormat, pFormatEtc->dwAspect));
+
+ logFormat(pFormatEtc->cfFormat);
+
+ return false;
+}
+
+void SharedClipboardWinDataObject::registerFormat(LPFORMATETC pFormatEtc, CLIPFORMAT clipFormat,
+ TYMED tyMed, LONG lIndex, DWORD dwAspect,
+ DVTARGETDEVICE *pTargetDevice)
+{
+ AssertPtr(pFormatEtc);
+
+ pFormatEtc->cfFormat = clipFormat;
+ pFormatEtc->tymed = tyMed;
+ pFormatEtc->lindex = lIndex;
+ pFormatEtc->dwAspect = dwAspect;
+ pFormatEtc->ptd = pTargetDevice;
+
+ LogFlowFunc(("Registered format=%ld\n", pFormatEtc->cfFormat));
+
+ logFormat(pFormatEtc->cfFormat);
+}
diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp
new file mode 100644
index 00000000..330ca7c8
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp
@@ -0,0 +1,205 @@
+/* $Id: ClipboardEnumFormatEtcImpl-win.cpp $ */
+/** @file
+ * ClipboardEnumFormatEtcImpl-win.cpp - Shared Clipboard IEnumFORMATETC ("Format et cetera") implementation.
+ */
+
+/*
+ * Copyright (C) 2013-2022 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 *
+*********************************************************************************************************************************/
+#include <new> /* For bad_alloc. */
+
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <VBox/GuestHost/SharedClipboard-win.h>
+
+#include <iprt/win/windows.h>
+
+#include <VBox/log.h>
+
+
+
+SharedClipboardWinEnumFormatEtc::SharedClipboardWinEnumFormatEtc(LPFORMATETC pFormatEtc, ULONG cFormats)
+ : m_lRefCount(1),
+ m_nIndex(0)
+{
+ HRESULT hr;
+
+ try
+ {
+ LogFlowFunc(("pFormatEtc=%p, cFormats=%RU32\n", pFormatEtc, cFormats));
+ m_pFormatEtc = new FORMATETC[cFormats];
+
+ for (ULONG i = 0; i < cFormats; i++)
+ {
+ LogFlowFunc(("Format %RU32: cfFormat=%RI16, tyMed=%RU32, dwAspect=%RU32\n",
+ i, pFormatEtc[i].cfFormat, pFormatEtc[i].tymed, pFormatEtc[i].dwAspect));
+
+ SharedClipboardWinDataObject::logFormat(pFormatEtc[i].cfFormat);
+
+ SharedClipboardWinEnumFormatEtc::CopyFormat(&m_pFormatEtc[i], &pFormatEtc[i]);
+ }
+
+ m_nNumFormats = cFormats;
+ hr = S_OK;
+ }
+ catch (std::bad_alloc &)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ LogFlowFunc(("hr=%Rhrc\n", hr));
+}
+
+SharedClipboardWinEnumFormatEtc::~SharedClipboardWinEnumFormatEtc(void)
+{
+ if (m_pFormatEtc)
+ {
+ for (ULONG i = 0; i < m_nNumFormats; i++)
+ {
+ if(m_pFormatEtc[i].ptd)
+ CoTaskMemFree(m_pFormatEtc[i].ptd);
+ }
+
+ delete[] m_pFormatEtc;
+ m_pFormatEtc = NULL;
+ }
+
+ LogFlowFunc(("m_lRefCount=%RI32\n", m_lRefCount));
+}
+
+/*
+ * IUnknown methods.
+ */
+
+STDMETHODIMP_(ULONG) SharedClipboardWinEnumFormatEtc::AddRef(void)
+{
+ return InterlockedIncrement(&m_lRefCount);
+}
+
+STDMETHODIMP_(ULONG) SharedClipboardWinEnumFormatEtc::Release(void)
+{
+ LONG lCount = InterlockedDecrement(&m_lRefCount);
+ if (lCount == 0)
+ {
+ LogFlowFunc(("Delete\n"));
+ delete this;
+ return 0;
+ }
+
+ return lCount;
+}
+
+STDMETHODIMP SharedClipboardWinEnumFormatEtc::QueryInterface(REFIID iid, void **ppvObject)
+{
+ if ( iid == IID_IEnumFORMATETC
+ || iid == IID_IUnknown)
+ {
+ AddRef();
+ *ppvObject = this;
+ return S_OK;
+ }
+
+ *ppvObject = 0;
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP SharedClipboardWinEnumFormatEtc::Next(ULONG cFormats, LPFORMATETC pFormatEtc, ULONG *pcFetched)
+{
+ ULONG ulCopied = 0;
+
+ if(cFormats == 0 || pFormatEtc == 0)
+ return E_INVALIDARG;
+
+ while ( m_nIndex < m_nNumFormats
+ && ulCopied < cFormats)
+ {
+ SharedClipboardWinEnumFormatEtc::CopyFormat(&pFormatEtc[ulCopied], &m_pFormatEtc[m_nIndex]);
+ ulCopied++;
+ m_nIndex++;
+ }
+
+ if (pcFetched)
+ *pcFetched = ulCopied;
+
+ return (ulCopied == cFormats) ? S_OK : S_FALSE;
+}
+
+STDMETHODIMP SharedClipboardWinEnumFormatEtc::Skip(ULONG cFormats)
+{
+ m_nIndex += cFormats;
+ return (m_nIndex <= m_nNumFormats) ? S_OK : S_FALSE;
+}
+
+STDMETHODIMP SharedClipboardWinEnumFormatEtc::Reset(void)
+{
+ m_nIndex = 0;
+ return S_OK;
+}
+
+STDMETHODIMP SharedClipboardWinEnumFormatEtc::Clone(IEnumFORMATETC **ppEnumFormatEtc)
+{
+ HRESULT hResult = CreateEnumFormatEtc(m_nNumFormats, m_pFormatEtc, ppEnumFormatEtc);
+ if (hResult == S_OK)
+ ((SharedClipboardWinEnumFormatEtc *) *ppEnumFormatEtc)->m_nIndex = m_nIndex;
+
+ return hResult;
+}
+
+/* static */
+void SharedClipboardWinEnumFormatEtc::CopyFormat(LPFORMATETC pDest, LPFORMATETC pSource)
+{
+ AssertPtrReturnVoid(pDest);
+ AssertPtrReturnVoid(pSource);
+
+ *pDest = *pSource;
+
+ if (pSource->ptd)
+ {
+ pDest->ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE));
+ *(pDest->ptd) = *(pSource->ptd);
+ }
+}
+
+/* static */
+HRESULT SharedClipboardWinEnumFormatEtc::CreateEnumFormatEtc(UINT nNumFormats, LPFORMATETC pFormatEtc, IEnumFORMATETC **ppEnumFormatEtc)
+{
+ AssertReturn(nNumFormats, E_INVALIDARG);
+ AssertPtrReturn(pFormatEtc, E_INVALIDARG);
+ AssertPtrReturn(ppEnumFormatEtc, E_INVALIDARG);
+
+ HRESULT hr;
+ try
+ {
+ *ppEnumFormatEtc = new SharedClipboardWinEnumFormatEtc(pFormatEtc, nNumFormats);
+ hr = S_OK;
+ }
+ catch(std::bad_alloc &)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ return hr;
+}
+
diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardMIME.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardMIME.cpp
new file mode 100644
index 00000000..b8e0a993
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/ClipboardMIME.cpp
@@ -0,0 +1,53 @@
+/* $Id: ClipboardMIME.cpp $ */
+/** @file
+ * Shared Clipboard - Path list class.
+ */
+
+/*
+ * Copyright (C) 2019-2022 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/GuestHost/SharedClipboard-transfers.h>
+
+#include <iprt/string.h>
+
+
+bool ShClMIMEHasFileURLs(const char *pcszFormat, size_t cchFormatMax)
+{
+ /** @todo "text/uri" also an official variant? */
+ return ( RTStrNICmp(pcszFormat, "text/uri-list", cchFormatMax) == 0
+ || RTStrNICmp(pcszFormat, "x-special/gnome-icon-list", cchFormatMax) == 0);
+}
+
+bool ShClMIMENeedsCache(const char *pcszFormat, size_t cchFormatMax)
+{
+ bool fNeedsDropDir = false;
+ if (!RTStrNICmp(pcszFormat, "text/uri-list", cchFormatMax)) /** @todo Add "x-special/gnome-icon-list"? */
+ fNeedsDropDir = true;
+
+ return fNeedsDropDir;
+}
+
diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardPath.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardPath.cpp
new file mode 100644
index 00000000..db49d7ee
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/ClipboardPath.cpp
@@ -0,0 +1,92 @@
+/* $Id: ClipboardPath.cpp $ */
+/** @file
+ * Shared Clipboard - Path handling.
+ */
+
+/*
+ * Copyright (C) 2019-2022 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/GuestHost/SharedClipboard-transfers.h>
+
+#include <iprt/err.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+
+/**
+ * Sanitizes the file name component so that unsupported characters
+ * will be replaced by an underscore ("_").
+ *
+ * @return IPRT status code.
+ * @param pszPath Path to sanitize.
+ * @param cbPath Size (in bytes) of path to sanitize.
+ */
+int ShClPathSanitizeFilename(char *pszPath, size_t cbPath)
+{
+ int rc = VINF_SUCCESS;
+#ifdef RT_OS_WINDOWS
+ RT_NOREF1(cbPath);
+ /* Replace out characters not allowed on Windows platforms, put in by RTTimeSpecToString(). */
+ /** @todo Use something like RTPathSanitize() if available later some time. */
+ static const RTUNICP s_uszValidRangePairs[] =
+ {
+ ' ', ' ',
+ '(', ')',
+ '-', '.',
+ '0', '9',
+ 'A', 'Z',
+ 'a', 'z',
+ '_', '_',
+ 0xa0, 0xd7af,
+ '\0'
+ };
+ ssize_t cReplaced = RTStrPurgeComplementSet(pszPath, s_uszValidRangePairs, '_' /* chReplacement */);
+ if (cReplaced < 0)
+ rc = VERR_INVALID_UTF8_ENCODING;
+#else
+ RT_NOREF2(pszPath, cbPath);
+#endif
+ return rc;
+}
+
+/**
+ * Sanitizes a given path regarding invalid / unhandled characters.
+ * Currently not implemented.
+ *
+ * @returns VBox status code.
+ * @param pszPath Path to sanitize. UTF-8.
+ * @param cbPath Size (in bytes) of the path to sanitize.
+ */
+int ShClPathSanitize(char *pszPath, size_t cbPath)
+{
+ RT_NOREF(pszPath, cbPath);
+
+ /** @todo */
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp
new file mode 100644
index 00000000..fc7b8274
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp
@@ -0,0 +1,382 @@
+/* $Id: ClipboardStreamImpl-win.cpp $ */
+/** @file
+ * ClipboardStreamImpl-win.cpp - Shared Clipboard IStream object implementation (guest and host side).
+ */
+
+/*
+ * Copyright (C) 2019-2022 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/GuestHost/SharedClipboard-win.h>
+
+#include <iprt/asm.h>
+#include <iprt/ldr.h>
+#include <iprt/thread.h>
+
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/GuestHost/SharedClipboard-win.h>
+#include <strsafe.h>
+
+#include <VBox/log.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+
+
+/*********************************************************************************************************************************
+* Static variables *
+*********************************************************************************************************************************/
+
+
+
+SharedClipboardWinStreamImpl::SharedClipboardWinStreamImpl(SharedClipboardWinDataObject *pParent, PSHCLTRANSFER pTransfer,
+ const Utf8Str &strPath, PSHCLFSOBJINFO pObjInfo)
+ : m_pParent(pParent)
+ , m_lRefCount(1) /* Our IDataObjct *always* holds the last reference to this object; needed for the callbacks. */
+ , m_pTransfer(pTransfer)
+ , m_hObj(SHCLOBJHANDLE_INVALID)
+ , m_strPath(strPath)
+ , m_objInfo(*pObjInfo)
+ , m_cbProcessed(0)
+ , m_fIsComplete(false)
+{
+ AssertPtr(m_pTransfer);
+
+ LogFunc(("m_strPath=%s\n", m_strPath.c_str()));
+}
+
+SharedClipboardWinStreamImpl::~SharedClipboardWinStreamImpl(void)
+{
+ LogFlowThisFuncEnter();
+}
+
+/*
+ * IUnknown methods.
+ */
+
+STDMETHODIMP SharedClipboardWinStreamImpl::QueryInterface(REFIID iid, void **ppvObject)
+{
+ AssertPtrReturn(ppvObject, E_INVALIDARG);
+
+ if (iid == IID_IUnknown)
+ {
+ LogFlowFunc(("IID_IUnknown\n"));
+ *ppvObject = (IUnknown *)(ISequentialStream *)this;
+ }
+ else if (iid == IID_ISequentialStream)
+ {
+ LogFlowFunc(("IID_ISequentialStream\n"));
+ *ppvObject = (ISequentialStream *)this;
+ }
+ else if (iid == IID_IStream)
+ {
+ LogFlowFunc(("IID_IStream\n"));
+ *ppvObject = (IStream *)this;
+ }
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG) SharedClipboardWinStreamImpl::AddRef(void)
+{
+ LONG lCount = InterlockedIncrement(&m_lRefCount);
+ LogFlowFunc(("lCount=%RI32\n", lCount));
+ return lCount;
+}
+
+STDMETHODIMP_(ULONG) SharedClipboardWinStreamImpl::Release(void)
+{
+ LONG lCount = InterlockedDecrement(&m_lRefCount);
+ LogFlowFunc(("lCount=%RI32\n", m_lRefCount));
+ if (lCount == 0)
+ {
+ delete this;
+ return 0;
+ }
+
+ return lCount;
+}
+
+/*
+ * IStream methods.
+ */
+
+STDMETHODIMP SharedClipboardWinStreamImpl::Clone(IStream** ppStream)
+{
+ RT_NOREF(ppStream);
+
+ LogFlowFuncEnter();
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::Commit(DWORD dwFrags)
+{
+ RT_NOREF(dwFrags);
+
+ LogFlowThisFuncEnter();
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::CopyTo(IStream *pDestStream, ULARGE_INTEGER nBytesToCopy, ULARGE_INTEGER *nBytesRead,
+ ULARGE_INTEGER *nBytesWritten)
+{
+ RT_NOREF(pDestStream, nBytesToCopy, nBytesRead, nBytesWritten);
+
+ LogFlowThisFuncEnter();
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::LockRegion(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes,DWORD dwFlags)
+{
+ RT_NOREF(nStart, nBytes, dwFlags);
+
+ LogFlowThisFuncEnter();
+ return STG_E_INVALIDFUNCTION;
+}
+
+/* Note: Windows seems to assume EOF if nBytesRead < nBytesToRead. */
+STDMETHODIMP SharedClipboardWinStreamImpl::Read(void *pvBuffer, ULONG nBytesToRead, ULONG *nBytesRead)
+{
+ LogFlowThisFunc(("Enter: m_cbProcessed=%RU64\n", m_cbProcessed));
+
+ /** @todo Is there any locking required so that parallel reads aren't possible? */
+
+ if (!pvBuffer)
+ return STG_E_INVALIDPOINTER;
+
+ if ( nBytesToRead == 0
+ || m_fIsComplete)
+ {
+ if (nBytesRead)
+ *nBytesRead = 0;
+ return S_OK;
+ }
+
+ int rc;
+
+ try
+ {
+ if ( m_hObj == SHCLOBJHANDLE_INVALID
+ && m_pTransfer->ProviderIface.pfnObjOpen)
+ {
+ SHCLOBJOPENCREATEPARMS openParms;
+ rc = ShClTransferObjOpenParmsInit(&openParms);
+ if (RT_SUCCESS(rc))
+ {
+ openParms.fCreate = SHCL_OBJ_CF_ACCESS_READ
+ | SHCL_OBJ_CF_ACCESS_DENYWRITE;
+
+ rc = RTStrCopy(openParms.pszPath, openParms.cbPath, m_strPath.c_str());
+ if (RT_SUCCESS(rc))
+ {
+ rc = m_pTransfer->ProviderIface.pfnObjOpen(&m_pTransfer->ProviderCtx, &openParms, &m_hObj);
+ }
+
+ ShClTransferObjOpenParmsDestroy(&openParms);
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ uint32_t cbRead = 0;
+
+ const uint64_t cbSize = (uint64_t)m_objInfo.cbObject;
+ const uint32_t cbToRead = RT_MIN(cbSize - m_cbProcessed, nBytesToRead);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (cbToRead)
+ {
+ rc = m_pTransfer->ProviderIface.pfnObjRead(&m_pTransfer->ProviderCtx, m_hObj,
+ pvBuffer, cbToRead, 0 /* fFlags */, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ m_cbProcessed += cbRead;
+ Assert(m_cbProcessed <= cbSize);
+ }
+ }
+
+ /* Transfer complete? Make sure to close the object again. */
+ m_fIsComplete = m_cbProcessed == cbSize;
+
+ if (m_fIsComplete)
+ {
+ if (m_pTransfer->ProviderIface.pfnObjClose)
+ {
+ int rc2 = m_pTransfer->ProviderIface.pfnObjClose(&m_pTransfer->ProviderCtx, m_hObj);
+ AssertRC(rc2);
+ }
+
+ if (m_pParent)
+ m_pParent->OnTransferComplete();
+ }
+ }
+
+ LogFlowThisFunc(("Leave: rc=%Rrc, cbSize=%RU64, cbProcessed=%RU64 -> nBytesToRead=%RU32, cbToRead=%RU32, cbRead=%RU32\n",
+ rc, cbSize, m_cbProcessed, nBytesToRead, cbToRead, cbRead));
+
+ if (nBytesRead)
+ *nBytesRead = (ULONG)cbRead;
+
+ if (nBytesToRead != cbRead)
+ return S_FALSE;
+
+ return S_OK;
+ }
+ catch (...)
+ {
+ LogFunc(("Caught exception\n"));
+ }
+
+ return E_FAIL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::Revert(void)
+{
+ LogFlowThisFuncEnter();
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::Seek(LARGE_INTEGER nMove, DWORD dwOrigin, ULARGE_INTEGER* nNewPos)
+{
+ RT_NOREF(nMove, dwOrigin, nNewPos);
+
+ LogFlowThisFunc(("nMove=%RI64, dwOrigin=%RI32\n", nMove, dwOrigin));
+
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::SetSize(ULARGE_INTEGER nNewSize)
+{
+ RT_NOREF(nNewSize);
+
+ LogFlowThisFuncEnter();
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::Stat(STATSTG *pStatStg, DWORD dwFlags)
+{
+ HRESULT hr = S_OK;
+
+ if (pStatStg)
+ {
+ RT_ZERO(*pStatStg);
+
+ switch (dwFlags)
+ {
+ case STATFLAG_NONAME:
+ pStatStg->pwcsName = NULL;
+ break;
+
+ case STATFLAG_DEFAULT:
+ {
+ /** @todo r=bird: This is using the wrong allocator. According to MSDN the
+ * caller will pass this to CoTaskMemFree, so we should use CoTaskMemAlloc to
+ * allocate it. */
+ int rc2 = RTStrToUtf16(m_strPath.c_str(), &pStatStg->pwcsName);
+ if (RT_FAILURE(rc2))
+ hr = E_FAIL;
+ break;
+ }
+
+ default:
+ hr = STG_E_INVALIDFLAG;
+ break;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ pStatStg->type = STGTY_STREAM;
+ pStatStg->grfMode = STGM_READ;
+ pStatStg->grfLocksSupported = 0;
+ pStatStg->cbSize.QuadPart = (uint64_t)m_objInfo.cbObject;
+ }
+ }
+ else
+ hr = STG_E_INVALIDPOINTER;
+
+ LogFlowThisFunc(("hr=%Rhrc\n", hr));
+ return hr;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::UnlockRegion(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes, DWORD dwFlags)
+{
+ RT_NOREF(nStart, nBytes, dwFlags);
+
+ LogFlowThisFuncEnter();
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::Write(const void *pvBuffer, ULONG nBytesToRead, ULONG *nBytesRead)
+{
+ RT_NOREF(pvBuffer, nBytesToRead, nBytesRead);
+
+ LogFlowThisFuncEnter();
+ return E_NOTIMPL;
+}
+
+/*
+ * Own stuff.
+ */
+
+/**
+ * Factory to create our own IStream implementation.
+ *
+ * @returns HRESULT
+ * @param pParent Pointer to the parent data object.
+ * @param pTransfer Pointer to Shared Clipboard transfer object to use.
+ * @param strPath Path of object to handle for the stream.
+ * @param pObjInfo Pointer to object information.
+ * @param ppStream Where to return the created stream object on success.
+ */
+/* static */
+HRESULT SharedClipboardWinStreamImpl::Create(SharedClipboardWinDataObject *pParent, PSHCLTRANSFER pTransfer,
+ const Utf8Str &strPath, PSHCLFSOBJINFO pObjInfo,
+ IStream **ppStream)
+{
+ AssertPtrReturn(pTransfer, E_POINTER);
+
+ SharedClipboardWinStreamImpl *pStream = new SharedClipboardWinStreamImpl(pParent, pTransfer, strPath, pObjInfo);
+ if (pStream)
+ {
+ pStream->AddRef();
+
+ *ppStream = pStream;
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
diff --git a/src/VBox/GuestHost/SharedClipboard/Makefile.kmk b/src/VBox/GuestHost/SharedClipboard/Makefile.kmk
new file mode 100644
index 00000000..92431ccc
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/Makefile.kmk
@@ -0,0 +1,36 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the VirtualBox Shared Clipboard guest/host code.
+#
+
+#
+# Copyright (C) 2020-2022 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
+
+ifdef VBOX_WITH_TESTCASES
+ include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp
new file mode 100644
index 00000000..929fec61
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp
@@ -0,0 +1,1226 @@
+/* $Id: clipboard-common.cpp $ */
+/** @file
+ * Shared Clipboard: Some helper function for converting between the various eol.
+ */
+
+/*
+ * Includes contributions from François Revol
+ *
+ * Copyright (C) 2006-2022 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
+ */
+
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/semaphore.h>
+#include <iprt/path.h>
+#include <iprt/rand.h>
+#include <iprt/utf16.h>
+
+#include <iprt/formats/bmp.h>
+
+#include <iprt/errcore.h>
+#include <VBox/log.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+
+
+/*********************************************************************************************************************************
+* Prototypes *
+*********************************************************************************************************************************/
+static void shClEventSourceResetInternal(PSHCLEVENTSOURCE pSource);
+
+static void shClEventDestroy(PSHCLEVENT pEvent);
+DECLINLINE(PSHCLEVENT) shclEventGet(PSHCLEVENTSOURCE pSource, SHCLEVENTID idEvent);
+
+
+/*********************************************************************************************************************************
+* Implementation *
+*********************************************************************************************************************************/
+
+/**
+ * Allocates a new event payload.
+ *
+ * @returns VBox status code.
+ * @param uID Payload ID to set for this payload. Useful for consequtive payloads.
+ * @param pvData Data block to associate to this payload.
+ * @param cbData Size (in bytes) of data block to associate.
+ * @param ppPayload Where to store the allocated event payload on success.
+ */
+int ShClPayloadAlloc(uint32_t uID, const void *pvData, uint32_t cbData,
+ PSHCLEVENTPAYLOAD *ppPayload)
+{
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData > 0, VERR_INVALID_PARAMETER);
+
+ PSHCLEVENTPAYLOAD pPayload = (PSHCLEVENTPAYLOAD)RTMemAlloc(sizeof(SHCLEVENTPAYLOAD));
+ if (pPayload)
+ {
+ pPayload->pvData = RTMemDup(pvData, cbData);
+ if (pPayload->pvData)
+ {
+ pPayload->cbData = cbData;
+ pPayload->uID = uID;
+
+ *ppPayload = pPayload;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pPayload);
+ }
+ return VERR_NO_MEMORY;
+}
+
+/**
+ * Frees an event payload.
+ *
+ * @returns VBox status code.
+ * @param pPayload Event payload to free.
+ */
+void ShClPayloadFree(PSHCLEVENTPAYLOAD pPayload)
+{
+ if (!pPayload)
+ return;
+
+ if (pPayload->pvData)
+ {
+ Assert(pPayload->cbData);
+ RTMemFree(pPayload->pvData);
+ pPayload->pvData = NULL;
+ }
+
+ pPayload->cbData = 0;
+ pPayload->uID = UINT32_MAX;
+
+ RTMemFree(pPayload);
+}
+
+/**
+ * Creates a new event source.
+ *
+ * @returns VBox status code.
+ * @param pSource Event source to create.
+ * @param uID ID to use for event source.
+ */
+int ShClEventSourceCreate(PSHCLEVENTSOURCE pSource, SHCLEVENTSOURCEID uID)
+{
+ LogFlowFunc(("pSource=%p, uID=%RU16\n", pSource, uID));
+ AssertPtrReturn(pSource, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectInit(&pSource->CritSect);
+ AssertRCReturn(rc, rc);
+
+ RTListInit(&pSource->lstEvents);
+
+ pSource->uID = uID;
+ /* Choose a random event ID starting point. */
+ pSource->idNextEvent = RTRandU32Ex(1, VBOX_SHCL_MAX_EVENTS - 1);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys an event source.
+ *
+ * @returns VBox status code.
+ * @param pSource Event source to destroy.
+ */
+int ShClEventSourceDestroy(PSHCLEVENTSOURCE pSource)
+{
+ if (!pSource)
+ return VINF_SUCCESS;
+
+ LogFlowFunc(("ID=%RU32\n", pSource->uID));
+
+ int rc = RTCritSectEnter(&pSource->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ shClEventSourceResetInternal(pSource);
+
+ rc = RTCritSectLeave(&pSource->CritSect);
+ AssertRC(rc);
+
+ RTCritSectDelete(&pSource->CritSect);
+
+ pSource->uID = UINT16_MAX;
+ pSource->idNextEvent = UINT32_MAX;
+ }
+
+ return rc;
+}
+
+/**
+ * Resets an event source, internal version.
+ *
+ * @param pSource Event source to reset.
+ */
+static void shClEventSourceResetInternal(PSHCLEVENTSOURCE pSource)
+{
+ LogFlowFunc(("ID=%RU32\n", pSource->uID));
+
+ PSHCLEVENT pEvIt;
+ PSHCLEVENT pEvItNext;
+ RTListForEachSafe(&pSource->lstEvents, pEvIt, pEvItNext, SHCLEVENT, Node)
+ {
+ RTListNodeRemove(&pEvIt->Node);
+
+ shClEventDestroy(pEvIt);
+
+ RTMemFree(pEvIt);
+ pEvIt = NULL;
+ }
+}
+
+/**
+ * Resets an event source.
+ *
+ * @param pSource Event source to reset.
+ */
+void ShClEventSourceReset(PSHCLEVENTSOURCE pSource)
+{
+ int rc2 = RTCritSectEnter(&pSource->CritSect);
+ if (RT_SUCCESS(rc2))
+ {
+ shClEventSourceResetInternal(pSource);
+
+ rc2 = RTCritSectLeave(&pSource->CritSect);
+ AssertRC(rc2);
+ }
+}
+
+/**
+ * Generates a new event ID for a specific event source and registers it.
+ *
+ * @returns VBox status code.
+ * @param pSource Event source to generate event for.
+ * @param ppEvent Where to return the new event generated on success.
+ */
+int ShClEventSourceGenerateAndRegisterEvent(PSHCLEVENTSOURCE pSource, PSHCLEVENT *ppEvent)
+{
+ AssertPtrReturn(pSource, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppEvent, VERR_INVALID_POINTER);
+
+ PSHCLEVENT pEvent = (PSHCLEVENT)RTMemAllocZ(sizeof(SHCLEVENT));
+ AssertReturn(pEvent, VERR_NO_MEMORY);
+ int rc = RTSemEventMultiCreate(&pEvent->hEvtMulSem);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectEnter(&pSource->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate an unique event ID.
+ */
+ for (uint32_t cTries = 0;; cTries++)
+ {
+ SHCLEVENTID idEvent = ++pSource->idNextEvent;
+ if (idEvent < VBOX_SHCL_MAX_EVENTS)
+ { /* likely */ }
+ else
+ pSource->idNextEvent = idEvent = 1; /* zero == error, remember! */
+
+ if (shclEventGet(pSource, idEvent) == NULL)
+ {
+ pEvent->pParent = pSource;
+ pEvent->idEvent = idEvent;
+ RTListAppend(&pSource->lstEvents, &pEvent->Node);
+
+ rc = RTCritSectLeave(&pSource->CritSect);
+ AssertRC(rc);
+
+ LogFlowFunc(("uSource=%RU16: New event: %#x\n", pSource->uID, idEvent));
+
+ ShClEventRetain(pEvent);
+ *ppEvent = pEvent;
+
+ return VINF_SUCCESS;
+ }
+
+ AssertBreak(cTries < 4096);
+ }
+
+ rc = RTCritSectLeave(&pSource->CritSect);
+ AssertRC(rc);
+ }
+ }
+
+ AssertMsgFailed(("Unable to register a new event ID for event source %RU16\n", pSource->uID));
+
+ RTSemEventMultiDestroy(pEvent->hEvtMulSem);
+ pEvent->hEvtMulSem = NIL_RTSEMEVENTMULTI;
+ RTMemFree(pEvent);
+ return rc;
+}
+
+/**
+ * Destroys an event.
+ *
+ * @param pEvent Event to destroy.
+ */
+static void shClEventDestroy(PSHCLEVENT pEvent)
+{
+ if (!pEvent)
+ return;
+
+ AssertMsgReturnVoid(pEvent->cRefs == 0, ("Event %RU32 still has %RU32 references\n",
+ pEvent->idEvent, pEvent->cRefs));
+
+ LogFlowFunc(("Event %RU32\n", pEvent->idEvent));
+
+ if (pEvent->hEvtMulSem != NIL_RTSEMEVENT)
+ {
+ RTSemEventMultiDestroy(pEvent->hEvtMulSem);
+ pEvent->hEvtMulSem = NIL_RTSEMEVENT;
+ }
+
+ ShClPayloadFree(pEvent->pPayload);
+
+ pEvent->idEvent = NIL_SHCLEVENTID;
+}
+
+/**
+ * Unregisters an event.
+ *
+ * @returns VBox status code.
+ * @param pSource Event source to unregister event for.
+ * @param pEvent Event to unregister. On success the pointer will be invalid.
+ */
+static int shClEventSourceUnregisterEventInternal(PSHCLEVENTSOURCE pSource, PSHCLEVENT pEvent)
+{
+ LogFlowFunc(("idEvent=%RU32, cRefs=%RU32\n", pEvent->idEvent, pEvent->cRefs));
+
+ AssertReturn(pEvent->cRefs == 0, VERR_WRONG_ORDER);
+
+ int rc = RTCritSectEnter(&pSource->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ RTListNodeRemove(&pEvent->Node);
+
+ shClEventDestroy(pEvent);
+
+ rc = RTCritSectLeave(&pSource->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ RTMemFree(pEvent);
+ pEvent = NULL;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Returns a specific event of a event source. Inlined version.
+ *
+ * @returns Pointer to event if found, or NULL if not found.
+ * @param pSource Event source to get event from.
+ * @param uID Event ID to get.
+ */
+DECLINLINE(PSHCLEVENT) shclEventGet(PSHCLEVENTSOURCE pSource, SHCLEVENTID idEvent)
+{
+ PSHCLEVENT pEvent;
+ RTListForEach(&pSource->lstEvents, pEvent, SHCLEVENT, Node)
+ {
+ if (pEvent->idEvent == idEvent)
+ return pEvent;
+ }
+
+ return NULL;
+}
+
+/**
+ * Returns a specific event of a event source.
+ *
+ * @returns Pointer to event if found, or NULL if not found.
+ * @param pSource Event source to get event from.
+ * @param idEvent ID of event to return.
+ */
+PSHCLEVENT ShClEventSourceGetFromId(PSHCLEVENTSOURCE pSource, SHCLEVENTID idEvent)
+{
+ AssertPtrReturn(pSource, NULL);
+
+ int rc = RTCritSectEnter(&pSource->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ PSHCLEVENT pEvent = shclEventGet(pSource, idEvent);
+
+ rc = RTCritSectLeave(&pSource->CritSect);
+ AssertRC(rc);
+
+ return pEvent;
+ }
+
+ return NULL;
+}
+
+/**
+ * Returns the last (newest) event ID which has been registered for an event source.
+ *
+ * @returns Pointer to last registered event, or NULL if not found.
+ * @param pSource Event source to get last registered event from.
+ */
+PSHCLEVENT ShClEventSourceGetLast(PSHCLEVENTSOURCE pSource)
+{
+ AssertPtrReturn(pSource, NULL);
+
+ int rc = RTCritSectEnter(&pSource->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ PSHCLEVENT pEvent = RTListGetLast(&pSource->lstEvents, SHCLEVENT, Node);
+
+ rc = RTCritSectLeave(&pSource->CritSect);
+ AssertRC(rc);
+
+ return pEvent;
+ }
+
+ return NULL;
+}
+
+/**
+ * Returns the current reference count for a specific event.
+ *
+ * @returns Reference count.
+ * @param pSource Event source the specific event is part of.
+ * @param idEvent Event ID to return reference count for.
+ */
+uint32_t ShClEventGetRefs(PSHCLEVENT pEvent)
+{
+ AssertPtrReturn(pEvent, 0);
+
+ return ASMAtomicReadU32(&pEvent->cRefs);
+}
+
+/**
+ * Detaches a payload from an event, internal version.
+ *
+ * @returns Pointer to the detached payload. Can be NULL if the payload has no payload.
+ * @param pEvent Event to detach payload for.
+ */
+static PSHCLEVENTPAYLOAD shclEventPayloadDetachInternal(PSHCLEVENT pEvent)
+{
+#ifdef VBOX_STRICT
+ AssertPtrReturn(pEvent, NULL);
+#endif
+
+ PSHCLEVENTPAYLOAD pPayload = pEvent->pPayload;
+
+ pEvent->pPayload = NULL;
+
+ return pPayload;
+}
+
+/**
+ * Waits for an event to get signalled.
+ *
+ * @returns VBox status code.
+ * @param pEvent Event to wait for.
+ * @param uTimeoutMs Timeout (in ms) to wait.
+ * @param ppPayload Where to store the (allocated) event payload on success. Needs to be free'd with
+ * SharedClipboardPayloadFree(). Optional.
+ */
+int ShClEventWait(PSHCLEVENT pEvent, RTMSINTERVAL uTimeoutMs, PSHCLEVENTPAYLOAD *ppPayload)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(ppPayload, VERR_INVALID_POINTER);
+ LogFlowFuncEnter();
+
+ int rc = RTSemEventMultiWait(pEvent->hEvtMulSem, uTimeoutMs);
+ if (RT_SUCCESS(rc))
+ {
+ if (ppPayload)
+ {
+ /* Make sure to detach payload here, as the caller now owns the data. */
+ *ppPayload = shclEventPayloadDetachInternal(pEvent);
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel2(("Shared Clipboard: Waiting for event %RU32 failed, rc=%Rrc\n", pEvent->idEvent, rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Retains an event by increasing its reference count.
+ *
+ * @returns New reference count, or UINT32_MAX if failed.
+ * @param pEvent Event to retain.
+ */
+uint32_t ShClEventRetain(PSHCLEVENT pEvent)
+{
+ AssertPtrReturn(pEvent, UINT32_MAX);
+ AssertReturn(ASMAtomicReadU32(&pEvent->cRefs) < 64, UINT32_MAX);
+ return ASMAtomicIncU32(&pEvent->cRefs);
+}
+
+/**
+ * Releases an event by decreasing its reference count.
+ *
+ * @returns New reference count, or UINT32_MAX if failed.
+ * @param pEvent Event to release.
+ * If the reference count reaches 0, the event will
+ * be destroyed and \a pEvent will be invalid.
+ */
+uint32_t ShClEventRelease(PSHCLEVENT pEvent)
+{
+ if (!pEvent)
+ return 0;
+
+ AssertReturn(ASMAtomicReadU32(&pEvent->cRefs) > 0, UINT32_MAX);
+
+ uint32_t const cRefs = ASMAtomicDecU32(&pEvent->cRefs);
+ if (cRefs == 0)
+ {
+ AssertPtr(pEvent->pParent);
+ int rc2 = shClEventSourceUnregisterEventInternal(pEvent->pParent, pEvent);
+ AssertRC(rc2);
+
+ return RT_SUCCESS(rc2) ? 0 : UINT32_MAX;
+ }
+
+ return cRefs;
+}
+
+/**
+ * Signals an event.
+ *
+ * @returns VBox status code.
+ * @param pEvent Event to signal.
+ * @param pPayload Event payload to associate. Takes ownership on
+ * success. Optional.
+ */
+int ShClEventSignal(PSHCLEVENT pEvent, PSHCLEVENTPAYLOAD pPayload)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+
+ Assert(pEvent->pPayload == NULL);
+
+ pEvent->pPayload = pPayload;
+
+ int rc = RTSemEventMultiSignal(pEvent->hEvtMulSem);
+ if (RT_FAILURE(rc))
+ pEvent->pPayload = NULL; /* (no race condition if consumer also enters the critical section) */
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+int ShClUtf16LenUtf8(PCRTUTF16 pcwszSrc, size_t cwcSrc, size_t *pchLen)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(pchLen, VERR_INVALID_POINTER);
+
+ size_t chLen = 0;
+ int rc = RTUtf16CalcUtf8LenEx(pcwszSrc, cwcSrc, &chLen);
+ if (RT_SUCCESS(rc))
+ *pchLen = chLen;
+ return rc;
+}
+
+int ShClConvUtf16CRLFToUtf8LF(PCRTUTF16 pcwszSrc, size_t cwcSrc,
+ char *pszBuf, size_t cbBuf, size_t *pcbLen)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertReturn (cwcSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbLen, VERR_INVALID_POINTER);
+
+ int rc;
+
+ PRTUTF16 pwszTmp = NULL;
+ size_t cchTmp = 0;
+
+ size_t cbLen = 0;
+
+ /* How long will the converted text be? */
+ rc = ShClUtf16CRLFLenUtf8(pcwszSrc, cwcSrc, &cchTmp);
+ if (RT_SUCCESS(rc))
+ {
+ cchTmp++; /* Add space for terminator. */
+
+ pwszTmp = (PRTUTF16)RTMemAlloc(cchTmp * sizeof(RTUTF16));
+ if (pwszTmp)
+ {
+ rc = ShClConvUtf16CRLFToLF(pcwszSrc, cwcSrc, pwszTmp, cchTmp);
+ if (RT_SUCCESS(rc))
+ rc = RTUtf16ToUtf8Ex(pwszTmp + 1, cchTmp - 1, &pszBuf, cbBuf, &cbLen);
+
+ RTMemFree(reinterpret_cast<void *>(pwszTmp));
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *pcbLen = cbLen;
+ }
+
+ return rc;
+}
+
+int ShClConvUtf16LFToCRLFA(PCRTUTF16 pcwszSrc, size_t cwcSrc,
+ PRTUTF16 *ppwszDst, size_t *pcwDst)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppwszDst, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcwDst, VERR_INVALID_POINTER);
+
+ PRTUTF16 pwszDst = NULL;
+ size_t cchDst;
+
+ int rc = ShClUtf16LFLenUtf8(pcwszSrc, cwcSrc, &cchDst);
+ if (RT_SUCCESS(rc))
+ {
+ pwszDst = (PRTUTF16)RTMemAlloc((cchDst + 1 /* Leave space for terminator */) * sizeof(RTUTF16));
+ if (pwszDst)
+ {
+ rc = ShClConvUtf16LFToCRLF(pcwszSrc, cwcSrc, pwszDst, cchDst + 1 /* Include terminator */);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppwszDst = pwszDst;
+ *pcwDst = cchDst;
+ }
+ else
+ RTMemFree(pwszDst);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+int ShClConvUtf8LFToUtf16CRLF(const char *pcszSrc, size_t cbSrc,
+ PRTUTF16 *ppwszDst, size_t *pcwDst)
+{
+ AssertPtrReturn(pcszSrc, VERR_INVALID_POINTER);
+ AssertReturn(cbSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppwszDst, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcwDst, VERR_INVALID_POINTER);
+
+ /* Intermediate conversion to UTF-16. */
+ size_t cwcTmp;
+ PRTUTF16 pwcTmp = NULL;
+ int rc = RTStrToUtf16Ex(pcszSrc, cbSrc, &pwcTmp, 0, &cwcTmp);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ShClConvUtf16LFToCRLFA(pwcTmp, cwcTmp, ppwszDst, pcwDst);
+ RTUtf16Free(pwcTmp);
+ }
+
+ return rc;
+}
+
+/**
+ * Converts a Latin-1 string with LF line endings into an UTF-16 string with CRLF endings.
+ *
+ * @returns VBox status code.
+ * @param pcszSrc Latin-1 string to convert.
+ * @param cbSrc Size (in bytes) of Latin-1 string to convert.
+ * @param ppwszDst Where to return the converted UTF-16 string on success.
+ * @param pcwDst Where to return the length (in UTF-16 characters) on success.
+ *
+ * @note Only converts the source until the string terminator is found (or length limit is hit).
+ */
+int ShClConvLatin1LFToUtf16CRLF(const char *pcszSrc, size_t cbSrc,
+ PRTUTF16 *ppwszDst, size_t *pcwDst)
+{
+ AssertPtrReturn(pcszSrc, VERR_INVALID_POINTER);
+ AssertReturn(cbSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppwszDst, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcwDst, VERR_INVALID_POINTER);
+
+ size_t chSrc = 0;
+
+ PRTUTF16 pwszDst = NULL;
+
+ /* Calculate the space needed. */
+ size_t cwDst = 0;
+ for (size_t i = 0; i < cbSrc && pcszSrc[i] != '\0'; ++i)
+ {
+ if (pcszSrc[i] == VBOX_SHCL_LINEFEED)
+ cwDst += 2; /* Space for VBOX_SHCL_CARRIAGERETURN + VBOX_SHCL_LINEFEED. */
+ else
+ ++cwDst;
+ chSrc++;
+ }
+
+ pwszDst = (PRTUTF16)RTMemAlloc((cwDst + 1 /* Leave space for the terminator */) * sizeof(RTUTF16));
+ AssertPtrReturn(pwszDst, VERR_NO_MEMORY);
+
+ /* Do the conversion, bearing in mind that Latin-1 expands "naturally" to UTF-16. */
+ for (size_t i = 0, j = 0; i < chSrc; ++i, ++j)
+ {
+ AssertMsg(j <= cwDst, ("cbSrc=%zu, j=%u vs. cwDst=%u\n", cbSrc, j, cwDst));
+ if (pcszSrc[i] != VBOX_SHCL_LINEFEED)
+ pwszDst[j] = pcszSrc[i];
+ else
+ {
+ pwszDst[j] = VBOX_SHCL_CARRIAGERETURN;
+ pwszDst[j + 1] = VBOX_SHCL_LINEFEED;
+ ++j;
+ }
+ }
+
+ pwszDst[cwDst] = '\0'; /* Make sure we are zero-terminated. */
+
+ *ppwszDst = pwszDst;
+ *pcwDst = cwDst;
+
+ return VINF_SUCCESS;
+}
+
+int ShClConvUtf16ToUtf8HTML(PCRTUTF16 pcwszSrc, size_t cwcSrc, char **ppszDst, size_t *pcbDst)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertReturn (cwcSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppszDst, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbDst, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ size_t cwTmp = cwcSrc;
+ PCRTUTF16 pwTmp = pcwszSrc;
+
+ char *pchDst = NULL;
+ size_t cbDst = 0;
+
+ size_t i = 0;
+ while (i < cwTmp)
+ {
+ /* Find zero symbol (end of string). */
+ for (; i < cwTmp && pcwszSrc[i] != 0; i++)
+ ;
+
+ /* Convert found string. */
+ char *psz = NULL;
+ size_t cch = 0;
+ rc = RTUtf16ToUtf8Ex(pwTmp, cwTmp, &psz, pwTmp - pcwszSrc, &cch);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Append new substring. */
+ char *pchNew = (char *)RTMemRealloc(pchDst, cbDst + cch + 1);
+ if (!pchNew)
+ {
+ RTStrFree(psz);
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pchDst = pchNew;
+ memcpy(pchDst + cbDst, psz, cch + 1);
+
+ RTStrFree(psz);
+
+ cbDst += cch + 1;
+
+ /* Skip zero symbols. */
+ for (; i < cwTmp && pcwszSrc[i] == 0; i++)
+ ;
+
+ /* Remember start of string. */
+ pwTmp += i;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppszDst = pchDst;
+ *pcbDst = cbDst;
+
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pchDst);
+
+ return rc;
+}
+
+int ShClUtf16LFLenUtf8(PCRTUTF16 pcwszSrc, size_t cwSrc, size_t *pchLen)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(pchLen, VERR_INVALID_POINTER);
+
+ AssertMsgReturn(pcwszSrc[0] != VBOX_SHCL_UTF16BEMARKER,
+ ("Big endian UTF-16 not supported yet\n"), VERR_NOT_SUPPORTED);
+
+ size_t cLen = 0;
+
+ /* Don't copy the endian marker. */
+ size_t i = pcwszSrc[0] == VBOX_SHCL_UTF16LEMARKER ? 1 : 0;
+
+ /* Calculate the size of the destination text string. */
+ /* Is this Utf16 or Utf16-LE? */
+ for (; i < cwSrc; ++i, ++cLen)
+ {
+ /* Check for a single line feed */
+ if (pcwszSrc[i] == VBOX_SHCL_LINEFEED)
+ ++cLen;
+#ifdef RT_OS_DARWIN
+ /* Check for a single carriage return (MacOS) */
+ if (pcwszSrc[i] == VBOX_SHCL_CARRIAGERETURN)
+ ++cLen;
+#endif
+ if (pcwszSrc[i] == 0)
+ {
+ /* Don't count this, as we do so below. */
+ break;
+ }
+ }
+
+ *pchLen = cLen;
+
+ return VINF_SUCCESS;
+}
+
+int ShClUtf16CRLFLenUtf8(PCRTUTF16 pcwszSrc, size_t cwSrc, size_t *pchLen)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertReturn(cwSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pchLen, VERR_INVALID_POINTER);
+
+ AssertMsgReturn(pcwszSrc[0] != VBOX_SHCL_UTF16BEMARKER,
+ ("Big endian UTF-16 not supported yet\n"), VERR_NOT_SUPPORTED);
+
+ size_t cLen = 0;
+
+ /* Calculate the size of the destination text string. */
+ /* Is this Utf16 or Utf16-LE? */
+ if (pcwszSrc[0] == VBOX_SHCL_UTF16LEMARKER)
+ cLen = 0;
+ else
+ cLen = 1;
+
+ for (size_t i = 0; i < cwSrc; ++i, ++cLen)
+ {
+ if ( (i + 1 < cwSrc)
+ && (pcwszSrc[i] == VBOX_SHCL_CARRIAGERETURN)
+ && (pcwszSrc[i + 1] == VBOX_SHCL_LINEFEED))
+ {
+ ++i;
+ }
+ if (pcwszSrc[i] == 0)
+ break;
+ }
+
+ *pchLen = cLen;
+
+ return VINF_SUCCESS;
+}
+
+int ShClConvUtf16LFToCRLF(PCRTUTF16 pcwszSrc, size_t cwcSrc, PRTUTF16 pu16Dst, size_t cwDst)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(pu16Dst, VERR_INVALID_POINTER);
+ AssertReturn(cwDst, VERR_INVALID_PARAMETER);
+
+ AssertMsgReturn(pcwszSrc[0] != VBOX_SHCL_UTF16BEMARKER,
+ ("Big endian UTF-16 not supported yet\n"), VERR_NOT_SUPPORTED);
+
+ int rc = VINF_SUCCESS;
+
+ /* Don't copy the endian marker. */
+ size_t i = pcwszSrc[0] == VBOX_SHCL_UTF16LEMARKER ? 1 : 0;
+ size_t j = 0;
+
+ for (; i < cwcSrc; ++i, ++j)
+ {
+ /* Don't copy the null byte, as we add it below. */
+ if (pcwszSrc[i] == 0)
+ break;
+
+ /* Not enough space in destination? */
+ if (j == cwDst)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ if (pcwszSrc[i] == VBOX_SHCL_LINEFEED)
+ {
+ pu16Dst[j] = VBOX_SHCL_CARRIAGERETURN;
+ ++j;
+
+ /* Not enough space in destination? */
+ if (j == cwDst)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ }
+#ifdef RT_OS_DARWIN
+ /* Check for a single carriage return (MacOS) */
+ else if (pcwszSrc[i] == VBOX_SHCL_CARRIAGERETURN)
+ {
+ /* Set CR.r */
+ pu16Dst[j] = VBOX_SHCL_CARRIAGERETURN;
+ ++j;
+
+ /* Not enough space in destination? */
+ if (j == cwDst)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ /* Add line feed. */
+ pu16Dst[j] = VBOX_SHCL_LINEFEED;
+ continue;
+ }
+#endif
+ pu16Dst[j] = pcwszSrc[i];
+ }
+
+ if (j == cwDst)
+ rc = VERR_BUFFER_OVERFLOW;
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Add terminator. */
+ pu16Dst[j] = 0;
+ }
+
+ return rc;
+}
+
+int ShClConvUtf16CRLFToLF(PCRTUTF16 pcwszSrc, size_t cwcSrc, PRTUTF16 pu16Dst, size_t cwDst)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertReturn(cwcSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pu16Dst, VERR_INVALID_POINTER);
+ AssertReturn(cwDst, VERR_INVALID_PARAMETER);
+
+ AssertMsgReturn(pcwszSrc[0] != VBOX_SHCL_UTF16BEMARKER,
+ ("Big endian UTF-16 not supported yet\n"), VERR_NOT_SUPPORTED);
+
+ /* Prepend the Utf16 byte order marker if it is missing. */
+ size_t cwDstPos;
+ if (pcwszSrc[0] == VBOX_SHCL_UTF16LEMARKER)
+ {
+ cwDstPos = 0;
+ }
+ else
+ {
+ pu16Dst[0] = VBOX_SHCL_UTF16LEMARKER;
+ cwDstPos = 1;
+ }
+
+ for (size_t i = 0; i < cwcSrc; ++i, ++cwDstPos)
+ {
+ if (pcwszSrc[i] == 0)
+ break;
+
+ if (cwDstPos == cwDst)
+ return VERR_BUFFER_OVERFLOW;
+
+ if ( (i + 1 < cwcSrc)
+ && (pcwszSrc[i] == VBOX_SHCL_CARRIAGERETURN)
+ && (pcwszSrc[i + 1] == VBOX_SHCL_LINEFEED))
+ {
+ ++i;
+ }
+
+ pu16Dst[cwDstPos] = pcwszSrc[i];
+ }
+
+ if (cwDstPos == cwDst)
+ return VERR_BUFFER_OVERFLOW;
+
+ /* Add terminating zero. */
+ pu16Dst[cwDstPos] = 0;
+
+ return VINF_SUCCESS;
+}
+
+int ShClDibToBmp(const void *pvSrc, size_t cbSrc, void **ppvDest, size_t *pcbDest)
+{
+ AssertPtrReturn(pvSrc, VERR_INVALID_POINTER);
+ AssertReturn(cbSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppvDest, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbDest, VERR_INVALID_POINTER);
+
+ PBMPWIN3XINFOHDR coreHdr = (PBMPWIN3XINFOHDR)pvSrc;
+ /** @todo Support all the many versions of the DIB headers. */
+ if ( cbSrc < sizeof(BMPWIN3XINFOHDR)
+ || RT_LE2H_U32(coreHdr->cbSize) < sizeof(BMPWIN3XINFOHDR)
+ || RT_LE2H_U32(coreHdr->cbSize) != sizeof(BMPWIN3XINFOHDR))
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ size_t offPixel = sizeof(BMPFILEHDR)
+ + RT_LE2H_U32(coreHdr->cbSize)
+ + RT_LE2H_U32(coreHdr->cClrUsed) * sizeof(uint32_t);
+ if (cbSrc < offPixel)
+ return VERR_INVALID_PARAMETER;
+
+ size_t cbDst = sizeof(BMPFILEHDR) + cbSrc;
+
+ void *pvDest = RTMemAlloc(cbDst);
+ if (!pvDest)
+ return VERR_NO_MEMORY;
+
+ PBMPFILEHDR fileHdr = (PBMPFILEHDR)pvDest;
+
+ fileHdr->uType = BMP_HDR_MAGIC;
+ fileHdr->cbFileSize = (uint32_t)RT_H2LE_U32(cbDst);
+ fileHdr->Reserved1 = 0;
+ fileHdr->Reserved2 = 0;
+ fileHdr->offBits = (uint32_t)RT_H2LE_U32(offPixel);
+
+ memcpy((uint8_t *)pvDest + sizeof(BMPFILEHDR), pvSrc, cbSrc);
+
+ *ppvDest = pvDest;
+ *pcbDest = cbDst;
+
+ return VINF_SUCCESS;
+}
+
+int ShClBmpGetDib(const void *pvSrc, size_t cbSrc, const void **ppvDest, size_t *pcbDest)
+{
+ AssertPtrReturn(pvSrc, VERR_INVALID_POINTER);
+ AssertReturn(cbSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppvDest, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbDest, VERR_INVALID_POINTER);
+
+ PBMPFILEHDR pBmpHdr = (PBMPFILEHDR)pvSrc;
+ if ( cbSrc < sizeof(BMPFILEHDR)
+ || pBmpHdr->uType != BMP_HDR_MAGIC
+ || RT_LE2H_U32(pBmpHdr->cbFileSize) != cbSrc)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ *ppvDest = ((uint8_t *)pvSrc) + sizeof(BMPFILEHDR);
+ *pcbDest = cbSrc - sizeof(BMPFILEHDR);
+
+ return VINF_SUCCESS;
+}
+
+#ifdef LOG_ENABLED
+
+int ShClDbgDumpHtml(const char *pcszSrc, size_t cbSrc)
+{
+ int rc = VINF_SUCCESS;
+ char *pszBuf = (char *)RTMemTmpAllocZ(cbSrc + 1);
+ if (pszBuf)
+ {
+ memcpy(pszBuf, pcszSrc, cbSrc);
+ pszBuf[cbSrc] = '\0';
+ for (size_t off = 0; off < cbSrc; ++off)
+ if (pszBuf[off] == '\n' || pszBuf[off] == '\r')
+ pszBuf[off] = ' ';
+ LogFunc(("Removed \\r\\n: %s\n", pszBuf));
+ RTMemTmpFree(pszBuf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+void ShClDbgDumpData(const void *pv, size_t cb, SHCLFORMAT uFormat)
+{
+ if (LogIsEnabled())
+ {
+ if (uFormat & VBOX_SHCL_FMT_UNICODETEXT)
+ {
+ LogFunc(("VBOX_SHCL_FMT_UNICODETEXT:\n"));
+ if (pv && cb)
+ LogFunc(("%ls\n", pv));
+ else
+ LogFunc(("%p %zu\n", pv, cb));
+ }
+ else if (uFormat & VBOX_SHCL_FMT_BITMAP)
+ LogFunc(("VBOX_SHCL_FMT_BITMAP\n"));
+ else if (uFormat & VBOX_SHCL_FMT_HTML)
+ {
+ LogFunc(("VBOX_SHCL_FMT_HTML:\n"));
+ if (pv && cb)
+ {
+ LogFunc(("%s\n", pv));
+ ShClDbgDumpHtml((const char *)pv, cb);
+ }
+ else
+ LogFunc(("%p %zu\n", pv, cb));
+ }
+ else
+ LogFunc(("Invalid format %02X\n", uFormat));
+ }
+}
+
+#endif /* LOG_ENABLED */
+
+/**
+ * Translates a Shared Clipboard host function number to a string.
+ *
+ * @returns Function ID string name.
+ * @param uFn The function to translate.
+ */
+const char *ShClHostFunctionToStr(uint32_t uFn)
+{
+ switch (uFn)
+ {
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_MODE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_HEADLESS);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_CANCEL);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_ERROR);
+ }
+ return "Unknown";
+}
+
+/**
+ * Translates a Shared Clipboard host message enum to a string.
+ *
+ * @returns Message ID string name.
+ * @param uMsg The message to translate.
+ */
+const char *ShClHostMsgToStr(uint32_t uMsg)
+{
+ switch (uMsg)
+ {
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_QUIT);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_READ_DATA);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_FORMATS_REPORT);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_CANCELED);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_READ_DATA_CID);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_STATUS);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_OPEN);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_CLOSE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_OPEN);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_CLOSE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_CANCEL);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ERROR);
+ }
+ return "Unknown";
+}
+
+/**
+ * Translates a Shared Clipboard guest message enum to a string.
+ *
+ * @returns Message ID string name.
+ * @param uMsg The message to translate.
+ */
+const char *ShClGuestMsgToStr(uint32_t uMsg)
+{
+ switch (uMsg)
+ {
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPORT_FORMATS);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_DATA_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_DATA_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_CONNECT);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPORT_FEATURES);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_QUERY_FEATURES);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_GET);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_CANCEL);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPLY);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_OPEN);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_CLOSE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_HDR_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_HDR_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_ENTRY_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_ENTRY_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_OPEN);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_CLOSE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ERROR);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_NEGOTIATE_CHUNK_SIZE);
+ }
+ return "Unknown";
+}
+
+/**
+ * Converts Shared Clipboard formats to a string.
+ *
+ * @returns Stringified Shared Clipboard formats, or NULL on failure. Must be free'd with RTStrFree().
+ * @param fFormats Shared Clipboard formats to convert.
+ *
+ */
+char *ShClFormatsToStrA(SHCLFORMATS fFormats)
+{
+#define APPEND_FMT_TO_STR(_aFmt) \
+ if (fFormats & VBOX_SHCL_FMT_##_aFmt) \
+ { \
+ if (pszFmts) \
+ { \
+ rc2 = RTStrAAppend(&pszFmts, ", "); \
+ if (RT_FAILURE(rc2)) \
+ break; \
+ } \
+ \
+ rc2 = RTStrAAppend(&pszFmts, #_aFmt); \
+ if (RT_FAILURE(rc2)) \
+ break; \
+ }
+
+ char *pszFmts = NULL;
+ int rc2 = VINF_SUCCESS;
+
+ do
+ {
+ APPEND_FMT_TO_STR(UNICODETEXT);
+ APPEND_FMT_TO_STR(BITMAP);
+ APPEND_FMT_TO_STR(HTML);
+# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ APPEND_FMT_TO_STR(URI_LIST);
+# endif
+
+ } while (0);
+
+ if (!pszFmts)
+ rc2 = RTStrAAppend(&pszFmts, "NONE");
+
+ if ( RT_FAILURE(rc2)
+ && pszFmts)
+ {
+ RTStrFree(pszFmts);
+ pszFmts = NULL;
+ }
+
+#undef APPEND_FMT_TO_STR
+
+ return pszFmts;
+}
+
diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp
new file mode 100644
index 00000000..4c0c6100
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp
@@ -0,0 +1,862 @@
+/* $Id: clipboard-transfers-http.cpp $ */
+/** @file
+ * Shared Clipboard: HTTP server implementation for Shared Clipboard transfers on UNIX-y guests / hosts.
+ */
+
+/*
+ * Copyright (C) 2020-2022 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 *
+*********************************************************************************************************************************/
+#include <signal.h>
+
+#include <iprt/http.h>
+#include <iprt/http-server.h>
+
+#include <iprt/net.h> /* To make use of IPv4Addr in RTGETOPTUNION. */
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/list.h>
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/uuid.h>
+#include <iprt/vfs.h>
+
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/GuestHost/SharedClipboard-x11.h>
+#include <VBox/GuestHost/SharedClipboard-transfers.h>
+
+
+/*********************************************************************************************************************************
+* Definitations *
+*********************************************************************************************************************************/
+
+typedef struct _SHCLHTTPSERVERTRANSFER
+{
+ /** The node list. */
+ RTLISTNODE Node;
+ /** Pointer to associated transfer. */
+ PSHCLTRANSFER pTransfer;
+ /** The (cached) root list of the transfer. NULL if not cached yet. */
+ PSHCLROOTLIST pRootList;
+ /** Critical section for serializing access. */
+ RTCRITSECT CritSect;
+ /** The handle we're going to use for this HTTP transfer. */
+ SHCLOBJHANDLE hObj;
+ /** The virtual path of the HTTP server's root directory for this transfer. */
+ char szPathVirtual[RTPATH_MAX];
+} SHCLHTTPSERVERTRANSFER;
+typedef SHCLHTTPSERVERTRANSFER *PSHCLHTTPSERVERTRANSFER;
+
+
+/*********************************************************************************************************************************
+* Prototypes *
+*********************************************************************************************************************************/
+static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pThis);
+static const char *shClTransferHttpServerGetHost(PSHCLHTTPSERVER pSrv);
+
+
+/*********************************************************************************************************************************
+* Public Shared Clipboard HTTP transfer functions *
+*********************************************************************************************************************************/
+
+/**
+ * Registers a Shared Clipboard transfer to a HTTP context.
+ *
+ * @returns VBox status code.
+ * @param pCtx HTTP context to register transfer for.
+ * @param pTransfer Transfer to register.
+ */
+int ShClHttpTransferRegister(PSHCLHTTPCONTEXT pCtx, PSHCLTRANSFER pTransfer)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Start the built-in HTTP server to serve file(s). */
+ if (!ShClTransferHttpServerIsRunning(&pCtx->HttpServer)) /* Only one HTTP server per transfer context. */
+ rc = ShClTransferHttpServerCreate(&pCtx->HttpServer, NULL /* puPort */);
+
+ if (RT_SUCCESS(rc))
+ rc = ShClTransferHttpServerRegisterTransfer(&pCtx->HttpServer, pTransfer);
+
+ return rc;
+}
+
+/**
+ * Unregisters a formerly registered Shared Clipboard transfer.
+ *
+ * @returns VBox status code.
+ * @param pCtx HTTP context to unregister transfer from.
+ * @param pTransfer Transfer to unregister.
+ */
+int ShClHttpTransferUnregister(PSHCLHTTPCONTEXT pCtx, PSHCLTRANSFER pTransfer)
+{
+ int rc = VINF_SUCCESS;
+
+ if (ShClTransferHttpServerIsRunning(&pCtx->HttpServer))
+ {
+ /* Try unregistering transfer (if it was registered before). */
+ rc = ShClTransferHttpServerUnregisterTransfer(&pCtx->HttpServer, pTransfer);
+ if (RT_SUCCESS(rc))
+ {
+ /* No more registered transfers left? Tear down the HTTP server instance then. */
+ if (ShClTransferHttpServerGetTransferCount(&pCtx->HttpServer) == 0)
+ rc = ShClTransferHttpServerDestroy(&pCtx->HttpServer);
+ }
+ AssertRC(rc);
+ }
+
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* Internal Shared Clipboard HTTP transfer functions *
+*********************************************************************************************************************************/
+
+DECLINLINE(void) shClHttpTransferLock(PSHCLHTTPSERVERTRANSFER pSrvTx)
+{
+ int rc2 = RTCritSectEnter(&pSrvTx->CritSect);
+ AssertRC(rc2);
+}
+
+DECLINLINE(void) shClHttpTransferUnlock(PSHCLHTTPSERVERTRANSFER pSrvTx)
+{
+ int rc2 = RTCritSectEnter(&pSrvTx->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Return the HTTP server transfer for a specific transfer ID.
+ *
+ * @returns Pointer to HTTP server transfer if found, NULL if not found.
+ * @param pSrv HTTP server instance.
+ * @param idTransfer Transfer ID to return HTTP server transfer for.
+ */
+static PSHCLHTTPSERVERTRANSFER shClTransferHttpServerGetTransferById(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer)
+{
+ PSHCLHTTPSERVERTRANSFER pSrvTx;
+ RTListForEach(&pSrv->lstTransfers, pSrvTx, SHCLHTTPSERVERTRANSFER, Node) /** @todo Slow O(n) lookup, but does it for now. */
+ {
+ if (pSrvTx->pTransfer->State.uID == idTransfer)
+ return pSrvTx;
+ }
+
+ return NULL;
+}
+
+/**
+ * Returns a HTTP server transfer from a given URL.
+ *
+ * @returns Pointer to HTTP server transfer if found, NULL if not found.
+ * @param pThis HTTP server instance data.
+ * @param pszUrl URL to validate.
+ */
+DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromUrl(PSHCLHTTPSERVER pThis, const char *pszUrl)
+{
+ AssertPtrReturn(pszUrl, NULL);
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx = NULL;
+
+ PSHCLHTTPSERVERTRANSFER pSrvTxCur;
+ RTListForEach(&pThis->lstTransfers, pSrvTxCur, SHCLHTTPSERVERTRANSFER, Node)
+ {
+ AssertPtr(pSrvTxCur->pTransfer);
+
+ LogFlowFunc(("pSrvTxCur=%s\n", pSrvTxCur->szPathVirtual));
+
+ /* Be picky here, do a case sensitive comparison. */
+ if (RTStrStartsWith(pszUrl, pSrvTxCur->szPathVirtual))
+ {
+ pSrvTx = pSrvTxCur;
+ break;
+ }
+ }
+
+ if (!pSrvTx)
+ LogRel2(("Shared Clipboard: HTTP URL '%s' not valid\n", pszUrl));
+
+ LogFlowFunc(("pszUrl=%s, pSrvTx=%p\n", pszUrl, pSrvTx));
+ return pSrvTx;
+}
+
+/**
+ * Returns a HTTP server transfer from an internal HTTP handle.
+ *
+ * @returns Pointer to HTTP server transfer if found, NULL if not found.
+ * @param pThis HTTP server instance data.
+ * @param pvHandle Handle to return transfer for.
+ */
+DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromHandle(PSHCLHTTPSERVER pThis, void *pvHandle)
+{
+ AssertPtrReturn(pvHandle, NULL);
+
+ const SHCLTRANSFERID uHandle = *(uint16_t *)pvHandle;
+
+ /** @ŧodo Use a handle lookup table (map) later. */
+ PSHCLHTTPSERVERTRANSFER pSrvTxCur;
+ RTListForEach(&pThis->lstTransfers, pSrvTxCur, SHCLHTTPSERVERTRANSFER, Node)
+ {
+ AssertPtr(pSrvTxCur->pTransfer);
+
+ if (pSrvTxCur->pTransfer->State.uID == uHandle) /** @ŧodo We're using the transfer ID as handle for now. */
+ return pSrvTxCur;
+ }
+
+ return NULL;
+}
+
+static int shClTransferHttpGetTransferRoots(PSHCLHTTPSERVER pThis, PSHCLHTTPSERVERTRANSFER pSrvTx)
+{
+ RT_NOREF(pThis);
+
+ int rc = VINF_SUCCESS;
+
+ if (pSrvTx->pRootList == NULL)
+ {
+ AssertPtr(pSrvTx->pTransfer);
+ rc = ShClTransferRootsGet(pSrvTx->pTransfer, &pSrvTx->pRootList);
+ }
+
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* HTTP server callback implementations *
+*********************************************************************************************************************************/
+
+/** @copydoc RTHTTPSERVERCALLBACKS::pfnOpen */
+static DECLCALLBACK(int) shClTransferHttpOpen(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void **ppvHandle)
+{
+ PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser;
+ Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
+
+ int rc;
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpGetTransferFromUrl(pThis, pReq->pszUrl);
+ if (pSrvTx)
+ {
+ shClHttpTransferLock(pSrvTx);
+
+ AssertPtr(pSrvTx->pTransfer);
+
+ SHCLOBJOPENCREATEPARMS openParms;
+ rc = ShClTransferObjOpenParmsInit(&openParms);
+ if (RT_SUCCESS(rc))
+ {
+ openParms.fCreate = SHCL_OBJ_CF_ACCESS_READ
+ | SHCL_OBJ_CF_ACCESS_DENYWRITE;
+
+ rc = RTStrCopy(openParms.pszPath, openParms.cbPath, "foo"); /** @ŧodo BUGBUG !!!! */
+ if (RT_SUCCESS(rc))
+ {
+ rc = ShClTransferObjOpen(pSrvTx->pTransfer, &openParms, &pSrvTx->hObj);
+ if (RT_SUCCESS(rc))
+ {
+ *ppvHandle = &pSrvTx->hObj;
+ LogRel2(("Shared Clipboard: HTTP transfer (handle %RU64) started ...\n", pSrvTx->hObj));
+ }
+ }
+
+ ShClTransferObjOpenParmsDestroy(&openParms);
+ }
+
+ shClHttpTransferUnlock(pSrvTx);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: Error starting HTTP transfer for '%s', rc=%Rrc\n", pReq->pszUrl, rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/** @copydoc RTHTTPSERVERCALLBACKS::pfnRead */
+static DECLCALLBACK(int) shClTransferHttpRead(PRTHTTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbBuf, size_t *pcbRead)
+{
+ PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser;
+ Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
+
+ RT_NOREF(pvBuf, cbBuf, pcbRead);
+
+ int rc;
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpGetTransferFromHandle(pThis, pvHandle);
+ if (pSrvTx)
+ {
+ Assert(pSrvTx->hObj != SHCLOBJHANDLE_INVALID);
+
+ uint32_t cbRead;
+ rc = ShClTransferObjRead(pSrvTx->pTransfer, pSrvTx->hObj, pvBuf, cbBuf, 0 /* fFlags */, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbRead = (uint32_t)cbRead;
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: Error reading HTTP transfer (handle %RU64), rc=%Rrc\n", pSrvTx->hObj, rc));
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/** @copydoc RTHTTPSERVERCALLBACKS::pfnClose */
+static DECLCALLBACK(int) shClTransferHttpClose(PRTHTTPCALLBACKDATA pData, void *pvHandle)
+{
+ PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser;
+ Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
+
+ int rc;
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpGetTransferFromHandle(pThis, pvHandle);
+ if (pSrvTx)
+ {
+ shClHttpTransferLock(pSrvTx);
+
+ Assert(pSrvTx->hObj != SHCLOBJHANDLE_INVALID);
+ rc = ShClTransferObjClose(pSrvTx->pTransfer, pSrvTx->hObj);
+ if (RT_SUCCESS(rc))
+ {
+ pSrvTx->hObj = SHCLOBJHANDLE_INVALID;
+ LogRel2(("Shared Clipboard: HTTP transfer %RU16 done\n", pSrvTx->pTransfer->State.uID));
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: Error closing HTTP transfer (handle %RU64), rc=%Rrc\n", pSrvTx->hObj, rc));
+
+ shClHttpTransferUnlock(pSrvTx);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/** @copydoc RTHTTPSERVERCALLBACKS::pfnQueryInfo */
+static DECLCALLBACK(int) shClTransferHttpQueryInfo(PRTHTTPCALLBACKDATA pData,
+ PRTHTTPSERVERREQ pReq, PRTFSOBJINFO pObjInfo, char **ppszMIMEHint)
+{
+ PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser;
+ Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
+
+ int rc;
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpGetTransferFromUrl(pThis, pReq->pszUrl);
+ if (pSrvTx)
+ {
+ shClHttpTransferLock(pSrvTx);
+
+ rc = shClTransferHttpGetTransferRoots(pThis, pSrvTx);
+
+ shClHttpTransferUnlock(pSrvTx);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ RT_NOREF(pObjInfo, ppszMIMEHint);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/** @copydoc RTHTTPSERVERCALLBACKS::pfnDestroy */
+static DECLCALLBACK(int) shClTransferHttpDestroy(PRTHTTPCALLBACKDATA pData)
+{
+ PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser;
+ Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
+
+ return shClTransferHttpServerDestroyInternal(pThis);
+}
+
+
+/*********************************************************************************************************************************
+* Internal Shared Clipboard HTTP server functions *
+*********************************************************************************************************************************/
+
+/**
+ * Destroys a Shared Clipboard HTTP server instance, internal version.
+ *
+ * @returns VBox status code.
+ * @param pSrv Shared Clipboard HTTP server instance to destroy.
+ */
+static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pSrv)
+{
+ PSHCLHTTPSERVERTRANSFER pSrvTx, pSrvTxNext;
+ RTListForEachSafe(&pSrv->lstTransfers, pSrvTx, pSrvTxNext, SHCLHTTPSERVERTRANSFER, Node)
+ {
+ RTListNodeRemove(&pSrvTx->Node);
+
+ RTMemFree(pSrvTx);
+ pSrvTx = NULL;
+ }
+
+ RTHttpServerResponseDestroy(&pSrv->Resp);
+
+ pSrv->hHTTPServer = NIL_RTHTTPSERVER;
+
+ int rc = VINF_SUCCESS;
+
+ if (RTCritSectIsInitialized(&pSrv->CritSect))
+ rc = RTCritSectDelete(&pSrv->CritSect);
+
+ return rc;
+}
+
+/**
+ * Locks the critical section of a Shared Clipboard HTTP server instance.
+ *
+ * @param pSrv Shared Clipboard HTTP server instance to lock.
+ */
+DECLINLINE(void) shClTransferHttpServerLock(PSHCLHTTPSERVER pSrv)
+{
+ int rc2 = RTCritSectEnter(&pSrv->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Unlocks the critical section of a Shared Clipboard HTTP server instance.
+ *
+ * @param pSrv Shared Clipboard HTTP server instance to unlock.
+ */
+DECLINLINE(void) shClTransferHttpServerUnlock(PSHCLHTTPSERVER pSrv)
+{
+ int rc2 = RTCritSectLeave(&pSrv->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Initializes a new Shared Clipboard HTTP server instance.
+ *
+ * @param pSrv HTTP server instance to initialize.
+ */
+static void shClTransferHttpServerInitInternal(PSHCLHTTPSERVER pSrv)
+{
+ pSrv->hHTTPServer = NIL_RTHTTPSERVER;
+ pSrv->uPort = 0;
+ RTListInit(&pSrv->lstTransfers);
+ pSrv->cTransfers = 0;
+ int rc2 = RTHttpServerResponseInit(&pSrv->Resp);
+ AssertRC(rc2);
+}
+
+
+/*********************************************************************************************************************************
+* Public Shared Clipboard HTTP server functions *
+*********************************************************************************************************************************/
+
+/**
+ * Initializes a new Shared Clipboard HTTP server instance.
+ *
+ * @param pSrv HTTP server instance to initialize.
+ */
+void ShClTransferHttpServerInit(PSHCLHTTPSERVER pSrv)
+{
+ AssertPtrReturnVoid(pSrv);
+
+ shClTransferHttpServerInitInternal(pSrv);
+}
+
+/**
+ * Creates a new Shared Clipboard HTTP server instance, extended version.
+ *
+ * @returns VBox status code.
+ * @param pSrv HTTP server instance to create.
+ * @param uPort TCP port number to use.
+ */
+int ShClTransferHttpServerCreateEx(PSHCLHTTPSERVER pSrv, uint16_t uPort)
+{
+ AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
+
+ RTHTTPSERVERCALLBACKS Callbacks;
+ RT_ZERO(Callbacks);
+
+ Callbacks.pfnOpen = shClTransferHttpOpen;
+ Callbacks.pfnRead = shClTransferHttpRead;
+ Callbacks.pfnClose = shClTransferHttpClose;
+ Callbacks.pfnQueryInfo = shClTransferHttpQueryInfo;
+ Callbacks.pfnDestroy = shClTransferHttpDestroy;
+
+ /* Note: The server always and *only* runs against the localhost interface. */
+ int rc = RTHttpServerCreate(&pSrv->hHTTPServer, "localhost", uPort, &Callbacks,
+ pSrv, sizeof(SHCLHTTPSERVER));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectInit(&pSrv->CritSect);
+ AssertRCReturn(rc, rc);
+
+ pSrv->uPort = uPort;
+
+ LogRel2(("Shared Clipboard: HTTP server running at port %RU16\n", pSrv->uPort));
+ }
+ else
+ {
+ int rc2 = shClTransferHttpServerDestroyInternal(pSrv);
+ AssertRC(rc2);
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: HTTP server failed to run, rc=%Rrc\n", rc));
+
+ return rc;
+}
+
+/**
+ * Creates a new Shared Clipboard HTTP server instance.
+ *
+ * This does automatic probing of TCP ports if one already is being used.
+ *
+ * @returns VBox status code.
+ * @param pSrv HTTP server instance to create.
+ * @param puPort Where to return the TCP port number being used on success. Optional.
+ */
+int ShClTransferHttpServerCreate(PSHCLHTTPSERVER pSrv, uint16_t *puPort)
+{
+ AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
+ /* puPort is optional. */
+
+ /** @todo Try favorite ports first (e.g. 8080, 8000, ...)? */
+
+ RTRAND hRand;
+ int rc = RTRandAdvCreateSystemFaster(&hRand); /* Should be good enough for this task. */
+ if (RT_SUCCESS(rc))
+ {
+ uint16_t uPort;
+ for (int i = 0; i < 32; i++)
+ {
+#ifdef DEBUG_andy
+ uPort = 8080; /* Make the port predictable, but only for me, mwahaha! :-). */
+#else
+ uPort = RTRandAdvU32Ex(hRand, 1024, UINT16_MAX);
+#endif
+ rc = ShClTransferHttpServerCreateEx(pSrv, (uint32_t)uPort);
+ if (RT_SUCCESS(rc))
+ {
+ if (puPort)
+ *puPort = uPort;
+ break;
+ }
+ }
+
+ RTRandAdvDestroy(hRand);
+ }
+
+ return rc;
+}
+
+/**
+ * Destroys a Shared Clipboard HTTP server instance.
+ *
+ * @returns VBox status code.
+ * @param pSrv HTTP server instance to destroy.
+ */
+int ShClTransferHttpServerDestroy(PSHCLHTTPSERVER pSrv)
+{
+ AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
+
+ if (pSrv->hHTTPServer == NIL_RTHTTPSERVER)
+ return VINF_SUCCESS;
+
+ Assert(pSrv->cTransfers == 0); /* Sanity. */
+
+ int rc = RTHttpServerDestroy(pSrv->hHTTPServer);
+ if (RT_SUCCESS(rc))
+ rc = shClTransferHttpServerDestroyInternal(pSrv);
+
+ if (RT_SUCCESS(rc))
+ LogRel2(("Shared Clipboard: HTTP server stopped\n"));
+ else
+ LogRel(("Shared Clipboard: HTTP server failed to stop, rc=%Rrc\n", rc));
+
+ return rc;
+}
+
+/**
+ * Registers a Shared Clipboard transfer to a HTTP server instance.
+ *
+ * @returns VBox status code.
+ * @param pSrv HTTP server instance to register transfer for.
+ * @param pTransfer Transfer to register.
+ */
+int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ AssertReturn(pTransfer->State.uID, VERR_INVALID_PARAMETER); /* Paranoia. */
+
+ shClTransferHttpServerLock(pSrv);
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)RTMemAllocZ(sizeof(SHCLHTTPSERVERTRANSFER));
+ AssertPtrReturn(pSrvTx, VERR_NO_MEMORY);
+
+ RTUUID Uuid;
+ int rc = RTUuidCreate(&Uuid);
+ if (RT_SUCCESS(rc))
+ {
+ char szUuid[64];
+ rc = RTUuidToStr(&Uuid, szUuid, sizeof(szUuid));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectInit(&pSrvTx->CritSect);
+ AssertRC(rc);
+
+ /* Create the virtual HTTP path for the transfer.
+ * Every transfer has a dedicated HTTP path. */
+#ifdef DEBUG_andy
+ ssize_t cch = RTStrPrintf2(pSrvTx->szPathVirtual, sizeof(pSrvTx->szPathVirtual), "/d1bbda60-80b7-45dc-a41c-ac4686c1d988/10664");
+#else
+ ssize_t cch = RTStrPrintf2(pSrvTx->szPathVirtual, sizeof(pSrvTx->szPathVirtual), "%s/%RU16/", szUuid, pTransfer->State.uID);
+#endif
+ AssertReturn(cch, VERR_BUFFER_OVERFLOW);
+
+ pSrvTx->pTransfer = pTransfer;
+ pSrvTx->pRootList = NULL;
+ pSrvTx->hObj = SHCLOBJHANDLE_INVALID;
+
+ RTListAppend(&pSrv->lstTransfers, &pSrvTx->Node);
+ pSrv->cTransfers++;
+
+ LogFunc(("pTransfer=%p, idTransfer=%RU16, szPath=%s -> %RU32 transfers\n",
+ pSrvTx->pTransfer, pSrvTx->pTransfer->State.uID, pSrvTx->szPathVirtual, pSrv->cTransfers));
+
+ LogRel2(("Shared Clipboard: Registered HTTP transfer %RU16, now %RU32 HTTP transfers total\n",
+ pTransfer->State.uID, pSrv->cTransfers));
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pSrvTx);
+
+ shClTransferHttpServerUnlock(pSrv);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Unregisters a formerly registered Shared Clipboard transfer.
+ *
+ * @returns VBox status code.
+ * @param pSrv HTTP server instance to unregister transfer from.
+ * @param pTransfer Transfer to unregister.
+ */
+int ShClTransferHttpServerUnregisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ shClTransferHttpServerLock(pSrv);
+
+ AssertReturn(pSrv->cTransfers, VERR_WRONG_ORDER);
+
+ int rc = VINF_SUCCESS;
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx;
+ RTListForEach(&pSrv->lstTransfers, pSrvTx, SHCLHTTPSERVERTRANSFER, Node)
+ {
+ AssertPtr(pSrvTx->pTransfer);
+ if (pSrvTx->pTransfer->State.uID == pTransfer->State.uID)
+ {
+ RTListNodeRemove(&pSrvTx->Node);
+
+ Assert(pSrv->cTransfers);
+ pSrv->cTransfers--;
+
+ LogFunc(("pTransfer=%p, idTransfer=%RU16, szPath=%s -> %RU32 transfers\n",
+ pSrvTx->pTransfer, pSrvTx->pTransfer->State.uID, pSrvTx->szPathVirtual, pSrv->cTransfers));
+
+ LogRel2(("Shared Clipboard: Unregistered HTTP transfer %RU16, now %RU32 HTTP transfers total\n",
+ pTransfer->State.uID, pSrv->cTransfers));
+
+ rc = RTCritSectDelete(&pSrvTx->CritSect);
+ AssertRC(rc);
+
+ RTMemFree(pSrvTx);
+ pSrvTx = NULL;
+
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ shClTransferHttpServerUnlock(pSrv);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns whether a specific transfer ID is registered with a HTTP server instance or not.
+ *
+ * @returns \c true if the transfer ID is registered, \c false if not.
+ * @param pSrv HTTP server instance.
+ * @param idTransfer Transfer ID to check for.
+ */
+bool ShClTransferHttpServerHasTransfer(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer)
+{
+ AssertPtrReturn(pSrv, false);
+
+ shClTransferHttpServerLock(pSrv);
+
+ const bool fRc = shClTransferHttpServerGetTransferById(pSrv, idTransfer) != NULL;
+
+ shClTransferHttpServerUnlock(pSrv);
+
+ return fRc;
+}
+
+/**
+ * Returns the used TCP port number of a HTTP server instance.
+ *
+ * @returns TCP port number. 0 if not specified yet.
+ * @param pSrv HTTP server instance to return port for.
+ */
+uint16_t ShClTransferHttpServerGetPort(PSHCLHTTPSERVER pSrv)
+{
+ AssertPtrReturn(pSrv, 0);
+
+ shClTransferHttpServerLock(pSrv);
+
+ const uint16_t uPort = pSrv->uPort;
+
+ shClTransferHttpServerUnlock(pSrv);
+
+ return uPort;
+}
+
+/**
+ * Returns the number of registered HTTP server transfers of a HTTP server instance.
+ *
+ * @returns Number of registered transfers.
+ * @param pSrv HTTP server instance to return registered transfers for.
+ */
+uint32_t ShClTransferHttpServerGetTransferCount(PSHCLHTTPSERVER pSrv)
+{
+ AssertPtrReturn(pSrv, 0);
+
+ shClTransferHttpServerLock(pSrv);
+
+ const uint32_t cTransfers = pSrv->cTransfers;
+
+ shClTransferHttpServerUnlock(pSrv);
+
+ return cTransfers;
+}
+
+/**
+ * Returns the host name (scheme) of a HTTP server instance.
+ *
+ * @param pSrv HTTP server instance to return host name (scheme) for.
+ *
+ * @returns Host name (scheme).
+ */
+static const char *shClTransferHttpServerGetHost(PSHCLHTTPSERVER pSrv)
+{
+ RT_NOREF(pSrv);
+ return "http://localhost"; /* Hardcoded for now. */
+}
+
+/**
+ * Returns an allocated string with a HTTP server instance's address.
+ *
+ * @returns Allocated string with a HTTP server instance's address, or NULL on OOM.
+ * Needs to be free'd by the caller using RTStrFree().
+ * @param pSrv HTTP server instance to return address for.
+ */
+char *ShClTransferHttpServerGetAddressA(PSHCLHTTPSERVER pSrv)
+{
+ AssertPtrReturn(pSrv, NULL);
+
+ shClTransferHttpServerLock(pSrv);
+
+ char *pszAddress = RTStrAPrintf2("%s:%RU16", shClTransferHttpServerGetHost(pSrv), pSrv->uPort);
+ AssertPtr(pszAddress);
+
+ shClTransferHttpServerUnlock(pSrv);
+
+ return pszAddress;
+}
+
+/**
+ * Returns an allocated string with the URL of a given Shared Clipboard transfer ID.
+ *
+ * @returns Allocated string with the URL of a given Shared Clipboard transfer ID, or NULL if not found.
+ * Needs to be free'd by the caller using RTStrFree().
+ * @param pSrv HTTP server instance to return URL for.
+ */
+char *ShClTransferHttpServerGetUrlA(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer)
+{
+ AssertPtrReturn(pSrv, NULL);
+ AssertReturn(idTransfer != NIL_SHCLTRANSFERID, NULL);
+
+ shClTransferHttpServerLock(pSrv);
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpServerGetTransferById(pSrv, idTransfer);
+ if (!pSrvTx)
+ {
+ AssertFailed();
+ shClTransferHttpServerUnlock(pSrv);
+ return NULL;
+ }
+
+ AssertReturn(RTStrNLen(pSrvTx->szPathVirtual, RTPATH_MAX), NULL);
+ char *pszUrl = RTStrAPrintf2("%s:%RU16/%s", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, pSrvTx->szPathVirtual);
+ AssertPtr(pszUrl);
+
+ shClTransferHttpServerUnlock(pSrv);
+
+ return pszUrl;
+}
+
+/**
+ * Returns whether a given HTTP server instance is running or not.
+ *
+ * @returns \c true if running, or \c false if not.
+ * @param pSrv HTTP server instance to check running state for.
+ */
+bool ShClTransferHttpServerIsRunning(PSHCLHTTPSERVER pSrv)
+{
+ AssertPtrReturn(pSrv, false);
+
+ return (pSrv->hHTTPServer != NIL_RTHTTPSERVER); /* Seems enough for now. */
+}
diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp
new file mode 100644
index 00000000..d2fd3393
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp
@@ -0,0 +1,3132 @@
+/* $Id: clipboard-transfers.cpp $ */
+/** @file
+ * Shared Clipboard: Common Shared Clipboard transfer handling code.
+ */
+
+/*
+ * Copyright (C) 2019-2022 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
+ */
+
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <VBox/log.h>
+
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/list.h>
+#include <iprt/path.h>
+#include <iprt/rand.h>
+#include <iprt/semaphore.h>
+
+#include <VBox/err.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/GuestHost/SharedClipboard-transfers.h>
+
+
+static int shClTransferThreadCreate(PSHCLTRANSFER pTransfer, PFNRTTHREAD pfnThreadFunc, void *pvUser);
+static int shClTransferThreadDestroy(PSHCLTRANSFER pTransfer, RTMSINTERVAL uTimeoutMs);
+
+static void shclTransferCtxTransferRemoveAndUnregister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer);
+static PSHCLTRANSFER shClTransferCtxGetTransferByIdInternal(PSHCLTRANSFERCTX pTransferCtx, uint32_t uId);
+static PSHCLTRANSFER shClTransferCtxGetTransferByIndexInternal(PSHCLTRANSFERCTX pTransferCtx, uint32_t uIdx);
+static int shClConvertFileCreateFlags(uint32_t fShClFlags, uint64_t *pfOpen);
+static int shClTransferResolvePathAbs(PSHCLTRANSFER pTransfer, const char *pszPath, uint32_t fFlags, char **ppszResolved);
+
+/** @todo Split this file up in different modules. */
+
+/**
+ * Allocates a new transfer root list.
+ *
+ * @returns Allocated transfer root list on success, or NULL on failure.
+ */
+PSHCLROOTLIST ShClTransferRootListAlloc(void)
+{
+ PSHCLROOTLIST pRootList = (PSHCLROOTLIST)RTMemAllocZ(sizeof(SHCLROOTLIST));
+
+ return pRootList;
+}
+
+/**
+ * Frees a transfer root list.
+ *
+ * @param pRootList transfer root list to free. The pointer will be
+ * invalid after returning from this function.
+ */
+void ShClTransferRootListFree(PSHCLROOTLIST pRootList)
+{
+ if (!pRootList)
+ return;
+
+ for (uint32_t i = 0; i < pRootList->Hdr.cRoots; i++)
+ ShClTransferListEntryInit(&pRootList->paEntries[i]);
+
+ RTMemFree(pRootList);
+ pRootList = NULL;
+}
+
+/**
+ * Initializes a transfer root list header.
+ *
+ * @returns VBox status code.
+ * @param pRootLstHdr Root list header to initialize.
+ */
+int ShClTransferRootListHdrInit(PSHCLROOTLISTHDR pRootLstHdr)
+{
+ AssertPtrReturn(pRootLstHdr, VERR_INVALID_POINTER);
+
+ RT_BZERO(pRootLstHdr, sizeof(SHCLROOTLISTHDR));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a transfer root list header.
+ *
+ * @param pRootLstHdr Root list header to destroy.
+ */
+void ShClTransferRootListHdrDestroy(PSHCLROOTLISTHDR pRootLstHdr)
+{
+ if (!pRootLstHdr)
+ return;
+
+ pRootLstHdr->fRoots = 0;
+ pRootLstHdr->cRoots = 0;
+}
+
+/**
+ * Duplicates a transfer list header.
+ *
+ * @returns Duplicated transfer list header on success, or NULL on failure.
+ * @param pRootLstHdr Root list header to duplicate.
+ */
+PSHCLROOTLISTHDR ShClTransferRootListHdrDup(PSHCLROOTLISTHDR pRootLstHdr)
+{
+ AssertPtrReturn(pRootLstHdr, NULL);
+
+ int rc = VINF_SUCCESS;
+
+ PSHCLROOTLISTHDR pRootsDup = (PSHCLROOTLISTHDR)RTMemAllocZ(sizeof(SHCLROOTLISTHDR));
+ if (pRootsDup)
+ {
+ *pRootsDup = *pRootLstHdr;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_FAILURE(rc))
+ {
+ ShClTransferRootListHdrDestroy(pRootsDup);
+ pRootsDup = NULL;
+ }
+
+ return pRootsDup;
+}
+
+/**
+ * (Deep) Copies a clipboard root list entry structure.
+ *
+ * @returns VBox status code.
+ * @param pDst Where to copy the source root list entry to.
+ * @param pSrc Source root list entry to copy.
+ */
+int ShClTransferRootListEntryCopy(PSHCLROOTLISTENTRY pDst, PSHCLROOTLISTENTRY pSrc)
+{
+ return ShClTransferListEntryCopy(pDst, pSrc);
+}
+
+/**
+ * Initializes a clipboard root list entry structure.
+ *
+ * @param pRootListEntry Clipboard root list entry structure to destroy.
+ */
+int ShClTransferRootListEntryInit(PSHCLROOTLISTENTRY pRootListEntry)
+{
+ return ShClTransferListEntryInit(pRootListEntry);
+}
+
+/**
+ * Destroys a clipboard root list entry structure.
+ *
+ * @param pRootListEntry Clipboard root list entry structure to destroy.
+ */
+void ShClTransferRootListEntryDestroy(PSHCLROOTLISTENTRY pRootListEntry)
+{
+ return ShClTransferListEntryDestroy(pRootListEntry);
+}
+
+/**
+ * Duplicates (allocates) a clipboard root list entry structure.
+ *
+ * @returns Duplicated clipboard root list entry structure on success.
+ * @param pRootListEntry Clipboard root list entry to duplicate.
+ */
+PSHCLROOTLISTENTRY ShClTransferRootListEntryDup(PSHCLROOTLISTENTRY pRootListEntry)
+{
+ return ShClTransferListEntryDup(pRootListEntry);
+}
+
+/**
+ * Initializes an list handle info structure.
+ *
+ * @returns VBox status code.
+ * @param pInfo List handle info structure to initialize.
+ */
+int ShClTransferListHandleInfoInit(PSHCLLISTHANDLEINFO pInfo)
+{
+ AssertPtrReturn(pInfo, VERR_INVALID_POINTER);
+
+ pInfo->hList = SHCLLISTHANDLE_INVALID;
+ pInfo->enmType = SHCLOBJTYPE_INVALID;
+
+ pInfo->pszPathLocalAbs = NULL;
+
+ RT_ZERO(pInfo->u);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a list handle info structure.
+ *
+ * @param pInfo List handle info structure to destroy.
+ */
+void ShClTransferListHandleInfoDestroy(PSHCLLISTHANDLEINFO pInfo)
+{
+ if (!pInfo)
+ return;
+
+ if (pInfo->pszPathLocalAbs)
+ {
+ RTStrFree(pInfo->pszPathLocalAbs);
+ pInfo->pszPathLocalAbs = NULL;
+ }
+}
+
+/**
+ * Allocates a transfer list header structure.
+ *
+ * @returns VBox status code.
+ * @param ppListHdr Where to store the allocated transfer list header structure on success.
+ */
+int ShClTransferListHdrAlloc(PSHCLLISTHDR *ppListHdr)
+{
+ int rc;
+
+ PSHCLLISTHDR pListHdr = (PSHCLLISTHDR)RTMemAllocZ(sizeof(SHCLLISTHDR));
+ if (pListHdr)
+ {
+ *ppListHdr = pListHdr;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Frees a transfer list header structure.
+ *
+ * @param pListEntry Transfer list header structure to free.
+ */
+void ShClTransferListHdrFree(PSHCLLISTHDR pListHdr)
+{
+ if (!pListHdr)
+ return;
+
+ LogFlowFuncEnter();
+
+ ShClTransferListHdrDestroy(pListHdr);
+
+ RTMemFree(pListHdr);
+ pListHdr = NULL;
+}
+
+/**
+ * Duplicates (allocates) a transfer list header structure.
+ *
+ * @returns Duplicated transfer list header structure on success.
+ * @param pListHdr Transfer list header to duplicate.
+ */
+PSHCLLISTHDR ShClTransferListHdrDup(PSHCLLISTHDR pListHdr)
+{
+ AssertPtrReturn(pListHdr, NULL);
+
+ PSHCLLISTHDR pListHdrDup = (PSHCLLISTHDR)RTMemAlloc(sizeof(SHCLLISTHDR));
+ if (pListHdrDup)
+ {
+ *pListHdrDup = *pListHdr;
+ }
+
+ return pListHdrDup;
+}
+
+/**
+ * Initializes a transfer list header structure.
+ *
+ * @returns VBox status code.
+ * @param pListHdr Transfer list header struct to initialize.
+ */
+int ShClTransferListHdrInit(PSHCLLISTHDR pListHdr)
+{
+ AssertPtrReturn(pListHdr, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ ShClTransferListHdrReset(pListHdr);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a transfer list header structure.
+ *
+ * @param pListHdr Transfer list header struct to destroy.
+ */
+void ShClTransferListHdrDestroy(PSHCLLISTHDR pListHdr)
+{
+ if (!pListHdr)
+ return;
+
+ LogFlowFuncEnter();
+}
+
+/**
+ * Resets a transfer list header structure.
+ *
+ * @returns VBox status code.
+ * @param pListHdr Transfer list header struct to reset.
+ */
+void ShClTransferListHdrReset(PSHCLLISTHDR pListHdr)
+{
+ AssertPtrReturnVoid(pListHdr);
+
+ LogFlowFuncEnter();
+
+ RT_BZERO(pListHdr, sizeof(SHCLLISTHDR));
+}
+
+/**
+ * Returns whether a given transfer list header is valid or not.
+ *
+ * @returns \c true if valid, \c false if not.
+ * @param pListHdr Transfer list header to validate.
+ */
+bool ShClTransferListHdrIsValid(PSHCLLISTHDR pListHdr)
+{
+ RT_NOREF(pListHdr);
+ return true; /** @todo Implement this. */
+}
+
+int ShClTransferListOpenParmsCopy(PSHCLLISTOPENPARMS pDst, PSHCLLISTOPENPARMS pSrc)
+{
+ AssertPtrReturn(pDst, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSrc, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ if (pSrc->pszFilter)
+ {
+ pDst->pszFilter = RTStrDup(pSrc->pszFilter);
+ if (!pDst->pszFilter)
+ rc = VERR_NO_MEMORY;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pSrc->pszPath)
+ {
+ pDst->pszPath = RTStrDup(pSrc->pszPath);
+ if (!pDst->pszPath)
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pDst->fList = pDst->fList;
+ pDst->cbFilter = pSrc->cbFilter;
+ pDst->cbPath = pSrc->cbPath;
+ }
+
+ return rc;
+}
+
+/**
+ * Duplicates a transfer list open parameters structure.
+ *
+ * @returns Duplicated transfer list open parameters structure on success, or NULL on failure.
+ * @param pParms Transfer list open parameters structure to duplicate.
+ */
+PSHCLLISTOPENPARMS ShClTransferListOpenParmsDup(PSHCLLISTOPENPARMS pParms)
+{
+ AssertPtrReturn(pParms, NULL);
+
+ PSHCLLISTOPENPARMS pParmsDup = (PSHCLLISTOPENPARMS)RTMemAllocZ(sizeof(SHCLLISTOPENPARMS));
+ if (!pParmsDup)
+ return NULL;
+
+ int rc = ShClTransferListOpenParmsCopy(pParmsDup, pParms);
+ if (RT_FAILURE(rc))
+ {
+ ShClTransferListOpenParmsDestroy(pParmsDup);
+
+ RTMemFree(pParmsDup);
+ pParmsDup = NULL;
+ }
+
+ return pParmsDup;
+}
+
+/**
+ * Initializes a transfer list open parameters structure.
+ *
+ * @returns VBox status code.
+ * @param pParms Transfer list open parameters structure to initialize.
+ */
+int ShClTransferListOpenParmsInit(PSHCLLISTOPENPARMS pParms)
+{
+ AssertPtrReturn(pParms, VERR_INVALID_POINTER);
+
+ RT_BZERO(pParms, sizeof(SHCLLISTOPENPARMS));
+
+ pParms->cbFilter = SHCL_TRANSFER_PATH_MAX; /** @todo Make this dynamic. */
+ pParms->pszFilter = RTStrAlloc(pParms->cbFilter);
+
+ pParms->cbPath = SHCL_TRANSFER_PATH_MAX; /** @todo Make this dynamic. */
+ pParms->pszPath = RTStrAlloc(pParms->cbPath);
+
+ LogFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a transfer list open parameters structure.
+ *
+ * @param pParms Transfer list open parameters structure to destroy.
+ */
+void ShClTransferListOpenParmsDestroy(PSHCLLISTOPENPARMS pParms)
+{
+ if (!pParms)
+ return;
+
+ if (pParms->pszFilter)
+ {
+ RTStrFree(pParms->pszFilter);
+ pParms->pszFilter = NULL;
+ }
+
+ if (pParms->pszPath)
+ {
+ RTStrFree(pParms->pszPath);
+ pParms->pszPath = NULL;
+ }
+}
+
+/**
+ * Creates (allocates) and initializes a clipboard list entry structure.
+ *
+ * @param ppDirData Where to return the created clipboard list entry structure on success.
+ */
+int ShClTransferListEntryAlloc(PSHCLLISTENTRY *ppListEntry)
+{
+ PSHCLLISTENTRY pListEntry = (PSHCLLISTENTRY)RTMemAlloc(sizeof(SHCLLISTENTRY));
+ if (!pListEntry)
+ return VERR_NO_MEMORY;
+
+ int rc = ShClTransferListEntryInit(pListEntry);
+ if (RT_SUCCESS(rc))
+ *ppListEntry = pListEntry;
+
+ return rc;
+}
+
+/**
+ * Frees a clipboard list entry structure.
+ *
+ * @param pListEntry Clipboard list entry structure to free.
+ */
+void ShClTransferListEntryFree(PSHCLLISTENTRY pListEntry)
+{
+ if (!pListEntry)
+ return;
+
+ ShClTransferListEntryDestroy(pListEntry);
+ RTMemFree(pListEntry);
+}
+
+/**
+ * (Deep) Copies a clipboard list entry structure.
+ *
+ * @returns VBox status code.
+ * @param pListEntry Clipboard list entry to copy.
+ */
+int ShClTransferListEntryCopy(PSHCLLISTENTRY pDst, PSHCLLISTENTRY pSrc)
+{
+ AssertPtrReturn(pDst, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSrc, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ *pDst = *pSrc;
+
+ if (pSrc->pszName)
+ {
+ pDst->pszName = RTStrDup(pSrc->pszName);
+ if (!pDst->pszName)
+ rc = VERR_NO_MEMORY;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pSrc->pvInfo)
+ {
+ pDst->pvInfo = RTMemDup(pSrc->pvInfo, pSrc->cbInfo);
+ if (pDst->pvInfo)
+ {
+ pDst->cbInfo = pSrc->cbInfo;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (pDst->pvInfo)
+ {
+ RTMemFree(pDst->pvInfo);
+ pDst->pvInfo = NULL;
+ pDst->cbInfo = 0;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Duplicates (allocates) a clipboard list entry structure.
+ *
+ * @returns Duplicated clipboard list entry structure on success.
+ * @param pListEntry Clipboard list entry to duplicate.
+ */
+PSHCLLISTENTRY ShClTransferListEntryDup(PSHCLLISTENTRY pListEntry)
+{
+ AssertPtrReturn(pListEntry, NULL);
+
+ int rc = VINF_SUCCESS;
+
+ PSHCLLISTENTRY pListEntryDup = (PSHCLLISTENTRY)RTMemAllocZ(sizeof(SHCLLISTENTRY));
+ if (pListEntryDup)
+ rc = ShClTransferListEntryCopy(pListEntryDup, pListEntry);
+
+ if (RT_FAILURE(rc))
+ {
+ ShClTransferListEntryDestroy(pListEntryDup);
+
+ RTMemFree(pListEntryDup);
+ pListEntryDup = NULL;
+ }
+
+ return pListEntryDup;
+}
+
+/**
+ * Initializes a clipboard list entry structure.
+ *
+ * @returns VBox status code.
+ * @param pListEntry Clipboard list entry structure to initialize.
+ */
+int ShClTransferListEntryInit(PSHCLLISTENTRY pListEntry)
+{
+ AssertPtrReturn(pListEntry, VERR_INVALID_POINTER);
+
+ RT_BZERO(pListEntry, sizeof(SHCLLISTENTRY));
+
+ pListEntry->pszName = RTStrAlloc(SHCLLISTENTRY_MAX_NAME);
+ if (!pListEntry->pszName)
+ return VERR_NO_MEMORY;
+
+ pListEntry->cbName = SHCLLISTENTRY_MAX_NAME;
+
+ pListEntry->pvInfo = (PSHCLFSOBJINFO)RTMemAlloc(sizeof(SHCLFSOBJINFO));
+ if (pListEntry->pvInfo)
+ {
+ pListEntry->cbInfo = sizeof(SHCLFSOBJINFO);
+ pListEntry->fInfo = VBOX_SHCL_INFO_FLAG_FSOBJINFO;
+
+ return VINF_SUCCESS;
+ }
+
+ return VERR_NO_MEMORY;
+}
+
+/**
+ * Destroys a clipboard list entry structure.
+ *
+ * @param pListEntry Clipboard list entry structure to destroy.
+ */
+void ShClTransferListEntryDestroy(PSHCLLISTENTRY pListEntry)
+{
+ if (!pListEntry)
+ return;
+
+ if (pListEntry->pszName)
+ {
+ RTStrFree(pListEntry->pszName);
+
+ pListEntry->pszName = NULL;
+ pListEntry->cbName = 0;
+ }
+
+ if (pListEntry->pvInfo)
+ {
+ RTMemFree(pListEntry->pvInfo);
+ pListEntry->pvInfo = NULL;
+ pListEntry->cbInfo = 0;
+ }
+}
+
+/**
+ * Returns whether a given clipboard list entry is valid or not.
+ *
+ * @returns \c true if valid, \c false if not.
+ * @param pListEntry Clipboard list entry to validate.
+ */
+bool ShClTransferListEntryIsValid(PSHCLLISTENTRY pListEntry)
+{
+ AssertPtrReturn(pListEntry, false);
+
+ if ( !pListEntry->pszName
+ || !pListEntry->cbName
+ || strlen(pListEntry->pszName) == 0
+ || strlen(pListEntry->pszName) > pListEntry->cbName /* Includes zero termination */ - 1)
+ {
+ return false;
+ }
+
+ if (pListEntry->cbInfo) /* cbInfo / pvInfo is optional. */
+ {
+ if (!pListEntry->pvInfo)
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Initializes a transfer object context.
+ *
+ * @returns VBox status code.
+ * @param pObjCtx transfer object context to initialize.
+ */
+int ShClTransferObjCtxInit(PSHCLCLIENTTRANSFEROBJCTX pObjCtx)
+{
+ AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ pObjCtx->uHandle = SHCLOBJHANDLE_INVALID;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a transfer object context.
+ *
+ * @param pObjCtx transfer object context to destroy.
+ */
+void ShClTransferObjCtxDestroy(PSHCLCLIENTTRANSFEROBJCTX pObjCtx)
+{
+ AssertPtrReturnVoid(pObjCtx);
+
+ LogFlowFuncEnter();
+}
+
+/**
+ * Returns if a transfer object context is valid or not.
+ *
+ * @returns \c true if valid, \c false if not.
+ * @param pObjCtx transfer object context to check.
+ */
+bool ShClTransferObjCtxIsValid(PSHCLCLIENTTRANSFEROBJCTX pObjCtx)
+{
+ return ( pObjCtx
+ && pObjCtx->uHandle != SHCLOBJHANDLE_INVALID);
+}
+
+/**
+ * Initializes an object handle info structure.
+ *
+ * @returns VBox status code.
+ * @param pInfo Object handle info structure to initialize.
+ */
+int ShClTransferObjHandleInfoInit(PSHCLOBJHANDLEINFO pInfo)
+{
+ AssertPtrReturn(pInfo, VERR_INVALID_POINTER);
+
+ pInfo->hObj = SHCLOBJHANDLE_INVALID;
+ pInfo->enmType = SHCLOBJTYPE_INVALID;
+
+ pInfo->pszPathLocalAbs = NULL;
+
+ RT_ZERO(pInfo->u);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys an object handle info structure.
+ *
+ * @param pInfo Object handle info structure to destroy.
+ */
+void ShClTransferObjHandleInfoDestroy(PSHCLOBJHANDLEINFO pInfo)
+{
+ if (!pInfo)
+ return;
+
+ if (pInfo->pszPathLocalAbs)
+ {
+ RTStrFree(pInfo->pszPathLocalAbs);
+ pInfo->pszPathLocalAbs = NULL;
+ }
+}
+
+/**
+ * Initializes a transfer object open parameters structure.
+ *
+ * @returns VBox status code.
+ * @param pParms transfer object open parameters structure to initialize.
+ */
+int ShClTransferObjOpenParmsInit(PSHCLOBJOPENCREATEPARMS pParms)
+{
+ AssertPtrReturn(pParms, VERR_INVALID_POINTER);
+
+ int rc;
+
+ RT_BZERO(pParms, sizeof(SHCLOBJOPENCREATEPARMS));
+
+ pParms->cbPath = RTPATH_MAX; /** @todo Make this dynamic. */
+ pParms->pszPath = RTStrAlloc(pParms->cbPath);
+ if (pParms->pszPath)
+ {
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Copies a transfer object open parameters structure from source to destination.
+ *
+ * @returns VBox status code.
+ * @param pParmsDst Where to copy the source transfer object open parameters to.
+ * @param pParmsSrc Which source transfer object open parameters to copy.
+ */
+int ShClTransferObjOpenParmsCopy(PSHCLOBJOPENCREATEPARMS pParmsDst, PSHCLOBJOPENCREATEPARMS pParmsSrc)
+{
+ int rc;
+
+ *pParmsDst = *pParmsSrc;
+
+ if (pParmsSrc->pszPath)
+ {
+ Assert(pParmsSrc->cbPath);
+ pParmsDst->pszPath = RTStrDup(pParmsSrc->pszPath);
+ if (pParmsDst->pszPath)
+ {
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Destroys a transfer object open parameters structure.
+ *
+ * @param pParms transfer object open parameters structure to destroy.
+ */
+void ShClTransferObjOpenParmsDestroy(PSHCLOBJOPENCREATEPARMS pParms)
+{
+ if (!pParms)
+ return;
+
+ if (pParms->pszPath)
+ {
+ RTStrFree(pParms->pszPath);
+ pParms->pszPath = NULL;
+ }
+}
+
+/**
+ * Returns a specific object handle info of a transfer.
+ *
+ * @returns Pointer to object handle info if found, or NULL if not found.
+ * @param pTransfer Clipboard transfer to get object handle info from.
+ * @param hObj Object handle of the object to get handle info for.
+ */
+DECLINLINE(PSHCLOBJHANDLEINFO) shClTransferObjGet(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj)
+{
+ PSHCLOBJHANDLEINFO pIt;
+ RTListForEach(&pTransfer->lstObj, pIt, SHCLOBJHANDLEINFO, Node) /** @todo Slooow ...but works for now. */
+ {
+ if (pIt->hObj == hObj)
+ return pIt;
+ }
+
+ return NULL;
+}
+
+/**
+ * Opens a transfer object.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to open the object for.
+ * @param pOpenCreateParms Open / create parameters of transfer object to open / create.
+ * @param phObj Where to store the handle of transfer object opened on success.
+ */
+int ShClTransferObjOpen(PSHCLTRANSFER pTransfer, PSHCLOBJOPENCREATEPARMS pOpenCreateParms, PSHCLOBJHANDLE phObj)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pOpenCreateParms, VERR_INVALID_POINTER);
+ AssertPtrReturn(phObj, VERR_INVALID_POINTER);
+ AssertMsgReturn(pTransfer->pszPathRootAbs, ("Transfer has no root path set\n"), VERR_INVALID_PARAMETER);
+ AssertMsgReturn(pOpenCreateParms->pszPath, ("No path in open/create params set\n"), VERR_INVALID_PARAMETER);
+
+ if (pTransfer->cObjHandles >= pTransfer->cMaxObjHandles)
+ return VERR_SHCLPB_MAX_OBJECTS_REACHED;
+
+ LogFlowFunc(("pszPath=%s, fCreate=0x%x\n", pOpenCreateParms->pszPath, pOpenCreateParms->fCreate));
+
+ int rc;
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLOBJHANDLEINFO pInfo = (PSHCLOBJHANDLEINFO)RTMemAllocZ(sizeof(SHCLOBJHANDLEINFO));
+ if (pInfo)
+ {
+ rc = ShClTransferObjHandleInfoInit(pInfo);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t fOpen;
+ rc = shClConvertFileCreateFlags(pOpenCreateParms->fCreate, &fOpen);
+ if (RT_SUCCESS(rc))
+ {
+ rc = shClTransferResolvePathAbs(pTransfer, pOpenCreateParms->pszPath, 0 /* fFlags */,
+ &pInfo->pszPathLocalAbs);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileOpen(&pInfo->u.Local.hFile, pInfo->pszPathLocalAbs, fOpen);
+ if (RT_SUCCESS(rc))
+ LogRel2(("Shared Clipboard: Opened file '%s'\n", pInfo->pszPathLocalAbs));
+ else
+ LogRel(("Shared Clipboard: Error opening file '%s': rc=%Rrc\n", pInfo->pszPathLocalAbs, rc));
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pInfo->hObj = pTransfer->uObjHandleNext++;
+ pInfo->enmType = SHCLOBJTYPE_FILE;
+
+ RTListAppend(&pTransfer->lstObj, &pInfo->Node);
+ pTransfer->cObjHandles++;
+
+ LogFlowFunc(("cObjHandles=%RU32\n", pTransfer->cObjHandles));
+
+ *phObj = pInfo->hObj;
+ }
+ else
+ {
+ ShClTransferObjHandleInfoDestroy(pInfo);
+ RTMemFree(pInfo);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnObjOpen)
+ rc = pTransfer->ProviderIface.pfnObjOpen(&pTransfer->ProviderCtx, pOpenCreateParms, phObj);
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Closes a transfer object.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer that contains the object to close.
+ * @param hObj Handle of transfer object to close.
+ */
+int ShClTransferObjClose(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLOBJHANDLEINFO pInfo = shClTransferObjGet(pTransfer, hObj);
+ if (pInfo)
+ {
+ switch (pInfo->enmType)
+ {
+ case SHCLOBJTYPE_DIRECTORY:
+ {
+ rc = RTDirClose(pInfo->u.Local.hDir);
+ if (RT_SUCCESS(rc))
+ {
+ pInfo->u.Local.hDir = NIL_RTDIR;
+
+ LogRel2(("Shared Clipboard: Closed directory '%s'\n", pInfo->pszPathLocalAbs));
+ }
+ else
+ LogRel(("Shared Clipboard: Closing directory '%s' failed with %Rrc\n", pInfo->pszPathLocalAbs, rc));
+ break;
+ }
+
+ case SHCLOBJTYPE_FILE:
+ {
+ rc = RTFileClose(pInfo->u.Local.hFile);
+ if (RT_SUCCESS(rc))
+ {
+ pInfo->u.Local.hFile = NIL_RTFILE;
+
+ LogRel2(("Shared Clipboard: Closed file '%s'\n", pInfo->pszPathLocalAbs));
+ }
+ else
+ LogRel(("Shared Clipboard: Closing file '%s' failed with %Rrc\n", pInfo->pszPathLocalAbs, rc));
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ RTListNodeRemove(&pInfo->Node);
+
+ Assert(pTransfer->cObjHandles);
+ pTransfer->cObjHandles--;
+
+ ShClTransferObjHandleInfoDestroy(pInfo);
+
+ RTMemFree(pInfo);
+ pInfo = NULL;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnObjClose)
+ {
+ rc = pTransfer->ProviderIface.pfnObjClose(&pTransfer->ProviderCtx, hObj);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Reads from a transfer object.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer that contains the object to read from.
+ * @param hObj Handle of transfer object to read from.
+ * @param pvBuf Buffer for where to store the read data.
+ * @param cbBuf Size (in bytes) of buffer.
+ * @param fFlags Read flags. Optional.
+ * @param pcbRead Where to return how much bytes were read on success. Optional.
+ */
+int ShClTransferObjRead(PSHCLTRANSFER pTransfer,
+ SHCLOBJHANDLE hObj, void *pvBuf, uint32_t cbBuf, uint32_t fFlags, uint32_t *pcbRead)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn (cbBuf, VERR_INVALID_PARAMETER);
+ /* pcbRead is optional. */
+ /** @todo Validate fFlags. */
+
+ int rc = VINF_SUCCESS;
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLOBJHANDLEINFO pInfo = shClTransferObjGet(pTransfer, hObj);
+ if (pInfo)
+ {
+ switch (pInfo->enmType)
+ {
+ case SHCLOBJTYPE_FILE:
+ {
+ size_t cbRead;
+ rc = RTFileRead(pInfo->u.Local.hFile, pvBuf, cbBuf, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ if (pcbRead)
+ *pcbRead = (uint32_t)cbRead;
+ }
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnObjRead)
+ {
+ rc = pTransfer->ProviderIface.pfnObjRead(&pTransfer->ProviderCtx, hObj, pvBuf, cbBuf, fFlags, pcbRead);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Writes to a transfer object.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer that contains the object to write to.
+ * @param hObj Handle of transfer object to write to.
+ * @param pvBuf Buffer of data to write.
+ * @param cbBuf Size (in bytes) of buffer to write.
+ * @param fFlags Write flags. Optional.
+ * @param pcbWritten How much bytes were writtenon success. Optional.
+ */
+int ShClTransferObjWrite(PSHCLTRANSFER pTransfer,
+ SHCLOBJHANDLE hObj, void *pvBuf, uint32_t cbBuf, uint32_t fFlags, uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn (cbBuf, VERR_INVALID_PARAMETER);
+ /* pcbWritten is optional. */
+
+ int rc = VINF_SUCCESS;
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLOBJHANDLEINFO pInfo = shClTransferObjGet(pTransfer, hObj);
+ if (pInfo)
+ {
+ switch (pInfo->enmType)
+ {
+ case SHCLOBJTYPE_FILE:
+ {
+ rc = RTFileWrite(pInfo->u.Local.hFile, pvBuf, cbBuf, (size_t *)pcbWritten);
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnObjWrite)
+ {
+ rc = pTransfer->ProviderIface.pfnObjWrite(&pTransfer->ProviderCtx, hObj, pvBuf, cbBuf, fFlags, pcbWritten);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Duplicates a transfer object data chunk.
+ *
+ * @returns Duplicated object data chunk on success, or NULL on failure.
+ * @param pDataChunk transfer object data chunk to duplicate.
+ */
+PSHCLOBJDATACHUNK ShClTransferObjDataChunkDup(PSHCLOBJDATACHUNK pDataChunk)
+{
+ if (!pDataChunk)
+ return NULL;
+
+ PSHCLOBJDATACHUNK pDataChunkDup = (PSHCLOBJDATACHUNK)RTMemAllocZ(sizeof(SHCLOBJDATACHUNK));
+ if (!pDataChunkDup)
+ return NULL;
+
+ if (pDataChunk->pvData)
+ {
+ Assert(pDataChunk->cbData);
+
+ pDataChunkDup->uHandle = pDataChunk->uHandle;
+ pDataChunkDup->pvData = RTMemDup(pDataChunk->pvData, pDataChunk->cbData);
+ pDataChunkDup->cbData = pDataChunk->cbData;
+ }
+
+ return pDataChunkDup;
+}
+
+/**
+ * Destroys a transfer object data chunk.
+ *
+ * @param pDataChunk transfer object data chunk to destroy.
+ */
+void ShClTransferObjDataChunkDestroy(PSHCLOBJDATACHUNK pDataChunk)
+{
+ if (!pDataChunk)
+ return;
+
+ if (pDataChunk->pvData)
+ {
+ Assert(pDataChunk->cbData);
+
+ RTMemFree(pDataChunk->pvData);
+
+ pDataChunk->pvData = NULL;
+ pDataChunk->cbData = 0;
+ }
+
+ pDataChunk->uHandle = 0;
+}
+
+/**
+ * Frees a transfer object data chunk.
+ *
+ * @param pDataChunk transfer object data chunk to free. The handed-in pointer will
+ * be invalid after calling this function.
+ */
+void ShClTransferObjDataChunkFree(PSHCLOBJDATACHUNK pDataChunk)
+{
+ if (!pDataChunk)
+ return;
+
+ ShClTransferObjDataChunkDestroy(pDataChunk);
+
+ RTMemFree(pDataChunk);
+ pDataChunk = NULL;
+}
+
+/**
+ * Creates a clipboard transfer.
+ *
+ * @returns VBox status code.
+ * @param ppTransfer Where to return the created Shared Clipboard transfer struct.
+ * Must be destroyed by ShClTransferDestroy().
+ */
+int ShClTransferCreate(PSHCLTRANSFER *ppTransfer)
+{
+ AssertPtrReturn(ppTransfer, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ PSHCLTRANSFER pTransfer = (PSHCLTRANSFER)RTMemAllocZ(sizeof(SHCLTRANSFER));
+ AssertPtrReturn(pTransfer, VERR_NO_MEMORY);
+
+ pTransfer->State.uID = 0;
+ pTransfer->State.enmStatus = SHCLTRANSFERSTATUS_NONE;
+ pTransfer->State.enmDir = SHCLTRANSFERDIR_UNKNOWN;
+ pTransfer->State.enmSource = SHCLSOURCE_INVALID;
+
+ pTransfer->Thread.hThread = NIL_RTTHREAD;
+ pTransfer->Thread.fCancelled = false;
+ pTransfer->Thread.fStarted = false;
+ pTransfer->Thread.fStop = false;
+
+ pTransfer->pszPathRootAbs = NULL;
+
+#ifdef DEBUG_andy
+ pTransfer->uTimeoutMs = RT_MS_5SEC;
+#else
+ pTransfer->uTimeoutMs = RT_MS_30SEC;
+#endif
+ pTransfer->cbMaxChunkSize = _64K; /** @todo Make this configurable. */
+ pTransfer->cMaxListHandles = _4K; /** @todo Ditto. */
+ pTransfer->cMaxObjHandles = _4K; /** @todo Ditto. */
+
+ pTransfer->pvUser = NULL;
+ pTransfer->cbUser = 0;
+
+ RTListInit(&pTransfer->lstList);
+ RTListInit(&pTransfer->lstObj);
+
+ pTransfer->cRoots = 0;
+ RTListInit(&pTransfer->lstRoots);
+
+ int rc = ShClEventSourceCreate(&pTransfer->Events, 0 /* uID */);
+ if (RT_SUCCESS(rc))
+ {
+ *ppTransfer = pTransfer;
+ }
+ else
+ {
+ if (pTransfer)
+ {
+ ShClTransferDestroy(pTransfer);
+ RTMemFree(pTransfer);
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Destroys a clipboard transfer context struct.
+ *
+ * @returns VBox status code.
+ * @param pTransferCtx Clipboard transfer to destroy.
+ */
+int ShClTransferDestroy(PSHCLTRANSFER pTransfer)
+{
+ if (!pTransfer)
+ return VINF_SUCCESS;
+
+ LogFlowFuncEnter();
+
+ int rc = shClTransferThreadDestroy(pTransfer, 30 * 1000 /* Timeout in ms */);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ ShClTransferReset(pTransfer);
+
+ ShClEventSourceDestroy(&pTransfer->Events);
+
+ LogFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Initializes a Shared Clipboard transfer object.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Transfer to initialize.
+ * @param enmDir Specifies the transfer direction of this transfer.
+ * @param enmSource Specifies the data source of the transfer.
+ */
+int ShClTransferInit(PSHCLTRANSFER pTransfer, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource)
+{
+ pTransfer->State.enmDir = enmDir;
+ pTransfer->State.enmSource = enmSource;
+
+ LogFlowFunc(("uID=%RU32, enmDir=%RU32, enmSource=%RU32\n",
+ pTransfer->State.uID, pTransfer->State.enmDir, pTransfer->State.enmSource));
+
+ pTransfer->State.enmStatus = SHCLTRANSFERSTATUS_INITIALIZED; /* Now we're ready to run. */
+
+ pTransfer->cListHandles = 0;
+ pTransfer->uListHandleNext = 1;
+
+ pTransfer->cObjHandles = 0;
+ pTransfer->uObjHandleNext = 1;
+
+ int rc = VINF_SUCCESS;
+
+ if (pTransfer->Callbacks.pfnOnInitialize)
+ rc = pTransfer->Callbacks.pfnOnInitialize(&pTransfer->CallbackCtx);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns a specific list handle info of a transfer.
+ *
+ * @returns Pointer to list handle info if found, or NULL if not found.
+ * @param pTransfer Clipboard transfer to get list handle info from.
+ * @param hList List handle of the list to get handle info for.
+ */
+DECLINLINE(PSHCLLISTHANDLEINFO) shClTransferListGetByHandle(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList)
+{
+ PSHCLLISTHANDLEINFO pIt;
+ RTListForEach(&pTransfer->lstList, pIt, SHCLLISTHANDLEINFO, Node) /** @todo Sloooow ... improve this. */
+ {
+ if (pIt->hList == hList)
+ return pIt;
+ }
+
+ return NULL;
+}
+
+/**
+ * Creates a new list handle (local only).
+ *
+ * @returns New List handle on success, or SHCLLISTHANDLE_INVALID on error.
+ * @param pTransfer Clipboard transfer to create new list handle for.
+ */
+DECLINLINE(SHCLLISTHANDLE) shClTransferListHandleNew(PSHCLTRANSFER pTransfer)
+{
+ return pTransfer->uListHandleNext++; /** @todo Good enough for now. Improve this later. */
+}
+
+/**
+ * Validates whether a given path matches our set of rules or not.
+ *
+ * @returns VBox status code.
+ * @param pcszPath Path to validate.
+ * @param fMustExist Whether the path to validate also must exist.
+ */
+static int shClTransferValidatePath(const char *pcszPath, bool fMustExist)
+{
+ int rc = VINF_SUCCESS;
+
+ if (!strlen(pcszPath))
+ rc = VERR_INVALID_PARAMETER;
+
+ if ( RT_SUCCESS(rc)
+ && !RTStrIsValidEncoding(pcszPath))
+ {
+ rc = VERR_INVALID_UTF8_ENCODING;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && RTStrStr(pcszPath, ".."))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && fMustExist)
+ {
+ RTFSOBJINFO objInfo;
+ rc = RTPathQueryInfo(pcszPath, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ if (!RTDirExists(pcszPath)) /* Path must exist. */
+ rc = VERR_PATH_NOT_FOUND;
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ if (!RTFileExists(pcszPath)) /* File must exist. */
+ rc = VERR_FILE_NOT_FOUND;
+ }
+ else /* Everything else (e.g. symbolic links) are not supported. */
+ {
+ LogRel2(("Shared Clipboard: Path '%s' contains a symbolic link or junktion, which are not supported\n", pcszPath));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel2(("Shared Clipboard: Validating path '%s' failed: %Rrc\n", pcszPath, rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Resolves a relative path of a specific transfer to its absolute path.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to resolve path for.
+ * @param pszPath Path to resolve.
+ * @param fFlags Resolve flags. Currently not used and must be 0.
+ * @param ppszResolved Where to store the allocated resolved path. Must be free'd by the called using RTStrFree().
+ */
+static int shClTransferResolvePathAbs(PSHCLTRANSFER pTransfer, const char *pszPath, uint32_t fFlags,
+ char **ppszResolved)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn (fFlags == 0, VERR_INVALID_PARAMETER);
+
+ LogFlowFunc(("pszPathRootAbs=%s, pszPath=%s\n", pTransfer->pszPathRootAbs, pszPath));
+
+ int rc = shClTransferValidatePath(pszPath, false /* fMustExist */);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszPathAbs = RTPathJoinA(pTransfer->pszPathRootAbs, pszPath);
+ if (pszPathAbs)
+ {
+ char szResolved[RTPATH_MAX];
+ size_t cbResolved = sizeof(szResolved);
+ rc = RTPathAbsEx(pTransfer->pszPathRootAbs, pszPathAbs, RTPATH_STR_F_STYLE_HOST, szResolved, &cbResolved);
+
+ RTStrFree(pszPathAbs);
+
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("pszResolved=%s\n", szResolved));
+
+ rc = VERR_PATH_NOT_FOUND; /* Play safe by default. */
+
+ /* Make sure the resolved path is part of the set of root entries. */
+ PSHCLLISTROOT pListRoot;
+ RTListForEach(&pTransfer->lstRoots, pListRoot, SHCLLISTROOT, Node)
+ {
+ if (RTPathStartsWith(szResolved, pListRoot->pszPathAbs))
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppszResolved = RTStrDup(szResolved);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: Resolving absolute path '%s' failed, rc=%Rrc\n", pszPath, rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Opens a list.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to handle.
+ * @param pOpenParms List open parameters to use for opening.
+ * @param phList Where to store the List handle of opened list on success.
+ */
+int ShClTransferListOpen(PSHCLTRANSFER pTransfer, PSHCLLISTOPENPARMS pOpenParms,
+ PSHCLLISTHANDLE phList)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pOpenParms, VERR_INVALID_POINTER);
+ AssertPtrReturn(phList, VERR_INVALID_POINTER);
+
+ int rc;
+
+ if (pTransfer->cListHandles == pTransfer->cMaxListHandles)
+ return VERR_SHCLPB_MAX_LISTS_REACHED;
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ LogFlowFunc(("pszPath=%s\n", pOpenParms->pszPath));
+
+ PSHCLLISTHANDLEINFO pInfo
+ = (PSHCLLISTHANDLEINFO)RTMemAllocZ(sizeof(SHCLLISTHANDLEINFO));
+ if (pInfo)
+ {
+ rc = ShClTransferListHandleInfoInit(pInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = shClTransferResolvePathAbs(pTransfer, pOpenParms->pszPath, 0 /* fFlags */, &pInfo->pszPathLocalAbs);
+ if (RT_SUCCESS(rc))
+ {
+ RTFSOBJINFO objInfo;
+ rc = RTPathQueryInfo(pInfo->pszPathLocalAbs, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ rc = RTDirOpen(&pInfo->u.Local.hDir, pInfo->pszPathLocalAbs);
+ if (RT_SUCCESS(rc))
+ {
+ pInfo->enmType = SHCLOBJTYPE_DIRECTORY;
+
+ LogRel2(("Shared Clipboard: Opening directory '%s'\n", pInfo->pszPathLocalAbs));
+ }
+ else
+ LogRel(("Shared Clipboard: Opening directory '%s' failed with %Rrc\n", pInfo->pszPathLocalAbs, rc));
+
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ rc = RTFileOpen(&pInfo->u.Local.hFile, pInfo->pszPathLocalAbs,
+ RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ pInfo->enmType = SHCLOBJTYPE_FILE;
+
+ LogRel2(("Shared Clipboard: Opening file '%s'\n", pInfo->pszPathLocalAbs));
+ }
+ else
+ LogRel(("Shared Clipboard: Opening file '%s' failed with %Rrc\n", pInfo->pszPathLocalAbs, rc));
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ if (RT_SUCCESS(rc))
+ {
+ pInfo->hList = shClTransferListHandleNew(pTransfer);
+
+ RTListAppend(&pTransfer->lstList, &pInfo->Node);
+ pTransfer->cListHandles++;
+
+ if (phList)
+ *phList = pInfo->hList;
+
+ LogFlowFunc(("pszPathLocalAbs=%s, hList=%RU64, cListHandles=%RU32\n",
+ pInfo->pszPathLocalAbs, pInfo->hList, pTransfer->cListHandles));
+ }
+ else
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ if (RTDirIsValid(pInfo->u.Local.hDir))
+ RTDirClose(pInfo->u.Local.hDir);
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ if (RTFileIsValid(pInfo->u.Local.hFile))
+ RTFileClose(pInfo->u.Local.hFile);
+ }
+ }
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ ShClTransferListHandleInfoDestroy(pInfo);
+
+ RTMemFree(pInfo);
+ pInfo = NULL;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnListOpen)
+ {
+ rc = pTransfer->ProviderIface.pfnListOpen(&pTransfer->ProviderCtx, pOpenParms, phList);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Closes a list.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to handle.
+ * @param hList Handle of list to close.
+ */
+int ShClTransferListClose(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ if (hList == SHCLLISTHANDLE_INVALID)
+ return VINF_SUCCESS;
+
+ int rc = VINF_SUCCESS;
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLLISTHANDLEINFO pInfo = shClTransferListGetByHandle(pTransfer, hList);
+ if (pInfo)
+ {
+ switch (pInfo->enmType)
+ {
+ case SHCLOBJTYPE_DIRECTORY:
+ {
+ if (RTDirIsValid(pInfo->u.Local.hDir))
+ {
+ RTDirClose(pInfo->u.Local.hDir);
+ pInfo->u.Local.hDir = NIL_RTDIR;
+ }
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ RTListNodeRemove(&pInfo->Node);
+
+ Assert(pTransfer->cListHandles);
+ pTransfer->cListHandles--;
+
+ RTMemFree(pInfo);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnListClose)
+ {
+ rc = pTransfer->ProviderIface.pfnListClose(&pTransfer->ProviderCtx, hList);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Adds a file to a list heaer.
+ *
+ * @returns VBox status code.
+ * @param pHdr List header to add file to.
+ * @param pszPath Path of file to add.
+ */
+static int shclTransferListHdrAddFile(PSHCLLISTHDR pHdr, const char *pszPath)
+{
+ uint64_t cbSize = 0;
+ int rc = RTFileQuerySizeByPath(pszPath, &cbSize);
+ if (RT_SUCCESS(rc))
+ {
+ pHdr->cbTotalSize += cbSize;
+ pHdr->cTotalObjects++;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Builds a list header, internal version.
+ *
+ * @returns VBox status code.
+ * @param pHdr Where to store the build list header.
+ * @param pcszSrcPath Source path of list.
+ * @param pcszDstPath Destination path of list.
+ * @param pcszDstBase Destination base path.
+ * @param cchDstBase Number of charaters of destination base path.
+ */
+static int shclTransferListHdrFromDir(PSHCLLISTHDR pHdr, const char *pcszPathAbs)
+{
+ AssertPtrReturn(pcszPathAbs, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pcszPathAbs=%s\n", pcszPathAbs));
+
+ RTFSOBJINFO objInfo;
+ int rc = RTPathQueryInfo(pcszPathAbs, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ RTDIR hDir;
+ rc = RTDirOpen(&hDir, pcszPathAbs);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbDirEntry = 0;
+ PRTDIRENTRYEX pDirEntry = NULL;
+ do
+ {
+ /* Retrieve the next directory entry. */
+ rc = RTDirReadExA(hDir, &pDirEntry, &cbDirEntry, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ /* Skip "." and ".." entries. */
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ break;
+
+ pHdr->cTotalObjects++;
+ break;
+ }
+ case RTFS_TYPE_FILE:
+ {
+ char *pszSrc = RTPathJoinA(pcszPathAbs, pDirEntry->szName);
+ if (pszSrc)
+ {
+ rc = shclTransferListHdrAddFile(pHdr, pszSrc);
+ RTStrFree(pszSrc);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ case RTFS_TYPE_SYMLINK:
+ {
+ /** @todo Not implemented yet. */
+ }
+
+ default:
+ break;
+ }
+
+ } while (RT_SUCCESS(rc));
+
+ RTDirReadExAFree(&pDirEntry, &cbDirEntry);
+ RTDirClose(hDir);
+ }
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ rc = shclTransferListHdrAddFile(pHdr, pcszPathAbs);
+ }
+ else if (RTFS_IS_SYMLINK(objInfo.Attr.fMode))
+ {
+ /** @todo Not implemented yet. */
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Retrieves the header of a Shared Clipboard list.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to handle.
+ * @param hList Handle of list to get header for.
+ * @param pHdr Where to store the returned list header information.
+ */
+int ShClTransferListGetHeader(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList,
+ PSHCLLISTHDR pHdr)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHdr, VERR_INVALID_POINTER);
+
+ int rc;
+
+ LogFlowFunc(("hList=%RU64\n", hList));
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLLISTHANDLEINFO pInfo = shClTransferListGetByHandle(pTransfer, hList);
+ if (pInfo)
+ {
+ rc = ShClTransferListHdrInit(pHdr);
+ if (RT_SUCCESS(rc))
+ {
+ switch (pInfo->enmType)
+ {
+ case SHCLOBJTYPE_DIRECTORY:
+ {
+ LogFlowFunc(("DirAbs: %s\n", pInfo->pszPathLocalAbs));
+
+ rc = shclTransferListHdrFromDir(pHdr, pInfo->pszPathLocalAbs);
+ break;
+ }
+
+ case SHCLOBJTYPE_FILE:
+ {
+ LogFlowFunc(("FileAbs: %s\n", pInfo->pszPathLocalAbs));
+
+ pHdr->cTotalObjects = 1;
+
+ RTFSOBJINFO objInfo;
+ rc = RTFileQueryInfo(pInfo->u.Local.hFile, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ pHdr->cbTotalSize = objInfo.cbObject;
+ }
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+
+ LogFlowFunc(("cTotalObj=%RU64, cbTotalSize=%RU64\n", pHdr->cTotalObjects, pHdr->cbTotalSize));
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnListHdrRead)
+ {
+ rc = pTransfer->ProviderIface.pfnListHdrRead(&pTransfer->ProviderCtx, hList, pHdr);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns the current transfer object for a Shared Clipboard transfer list.
+ *
+ * Currently not implemented and wil return NULL.
+ *
+ * @returns Pointer to transfer object, or NULL if not found / invalid.
+ * @param pTransfer Clipboard transfer to return transfer object for.
+ * @param hList Handle of Shared Clipboard transfer list to get object for.
+ * @param uIdx Index of object to get.
+ */
+PSHCLTRANSFEROBJ ShClTransferListGetObj(PSHCLTRANSFER pTransfer,
+ SHCLLISTHANDLE hList, uint64_t uIdx)
+{
+ AssertPtrReturn(pTransfer, NULL);
+
+ RT_NOREF(hList, uIdx);
+
+ LogFlowFunc(("hList=%RU64\n", hList));
+
+ return NULL;
+}
+
+/**
+ * Reads a single Shared Clipboard list entry.
+ *
+ * @returns VBox status code or VERR_NO_MORE_FILES if the end of the list has been reached.
+ * @param pTransfer Clipboard transfer to handle.
+ * @param hList List handle of list to read from.
+ * @param pEntry Where to store the read information.
+ */
+int ShClTransferListRead(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList,
+ PSHCLLISTENTRY pEntry)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pEntry, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("hList=%RU64\n", hList));
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLLISTHANDLEINFO pInfo = shClTransferListGetByHandle(pTransfer, hList);
+ if (pInfo)
+ {
+ switch (pInfo->enmType)
+ {
+ case SHCLOBJTYPE_DIRECTORY:
+ {
+ LogFlowFunc(("\tDirectory: %s\n", pInfo->pszPathLocalAbs));
+
+ for (;;)
+ {
+ bool fSkipEntry = false; /* Whether to skip an entry in the enumeration. */
+
+ size_t cbDirEntry = 0;
+ PRTDIRENTRYEX pDirEntry = NULL;
+ rc = RTDirReadExA(pInfo->u.Local.hDir, &pDirEntry, &cbDirEntry, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ /* Skip "." and ".." entries. */
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ {
+ fSkipEntry = true;
+ break;
+ }
+
+ LogFlowFunc(("Directory: %s\n", pDirEntry->szName));
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ LogFlowFunc(("File: %s\n", pDirEntry->szName));
+ break;
+ }
+
+ case RTFS_TYPE_SYMLINK:
+ {
+ rc = VERR_NOT_IMPLEMENTED; /** @todo Not implemented yet. */
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && !fSkipEntry)
+ {
+ rc = RTStrCopy(pEntry->pszName, pEntry->cbName, pDirEntry->szName);
+ if (RT_SUCCESS(rc))
+ {
+ pEntry->cbName = (uint32_t)strlen(pEntry->pszName) + 1; /* Include termination. */
+
+ AssertPtr(pEntry->pvInfo);
+ Assert (pEntry->cbInfo == sizeof(SHCLFSOBJINFO));
+
+ ShClFsObjFromIPRT(PSHCLFSOBJINFO(pEntry->pvInfo), &pDirEntry->Info);
+
+ LogFlowFunc(("Entry pszName=%s, pvInfo=%p, cbInfo=%RU32\n",
+ pEntry->pszName, pEntry->pvInfo, pEntry->cbInfo));
+ }
+ }
+
+ RTDirReadExAFree(&pDirEntry, &cbDirEntry);
+ }
+
+ if ( !fSkipEntry /* Do we have a valid entry? Bail out. */
+ || RT_FAILURE(rc))
+ {
+ break;
+ }
+ }
+
+ break;
+ }
+
+ case SHCLOBJTYPE_FILE:
+ {
+ LogFlowFunc(("\tSingle file: %s\n", pInfo->pszPathLocalAbs));
+
+ RTFSOBJINFO objInfo;
+ rc = RTFileQueryInfo(pInfo->u.Local.hFile, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ pEntry->pvInfo = (PSHCLFSOBJINFO)RTMemAlloc(sizeof(SHCLFSOBJINFO));
+ if (pEntry->pvInfo)
+ {
+ rc = RTStrCopy(pEntry->pszName, pEntry->cbName, pInfo->pszPathLocalAbs);
+ if (RT_SUCCESS(rc))
+ {
+ ShClFsObjFromIPRT(PSHCLFSOBJINFO(pEntry->pvInfo), &objInfo);
+
+ pEntry->cbInfo = sizeof(SHCLFSOBJINFO);
+ pEntry->fInfo = VBOX_SHCL_INFO_FLAG_FSOBJINFO;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnListEntryRead)
+ rc = pTransfer->ProviderIface.pfnListEntryRead(&pTransfer->ProviderCtx, hList, pEntry);
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+int ShClTransferListWrite(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList,
+ PSHCLLISTENTRY pEntry)
+{
+ RT_NOREF(pTransfer, hList, pEntry);
+
+ int rc = VINF_SUCCESS;
+
+#if 0
+ if (pTransfer->ProviderIface.pfnListEntryWrite)
+ rc = pTransfer->ProviderIface.pfnListEntryWrite(&pTransfer->ProviderCtx, hList, pEntry);
+#endif
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns whether a given list handle is valid or not.
+ *
+ * @returns \c true if list handle is valid, \c false if not.
+ * @param pTransfer Clipboard transfer to handle.
+ * @param hList List handle to check.
+ */
+bool ShClTransferListHandleIsValid(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList)
+{
+ bool fIsValid = false;
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ fIsValid = shClTransferListGetByHandle(pTransfer, hList) != NULL;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ AssertFailed(); /** @todo Implement. */
+ }
+ else
+ AssertFailedStmt(fIsValid = false);
+
+ return fIsValid;
+}
+
+/**
+ * Copies a transfer callback table from source to destination.
+ *
+ * @param pCallbacksDst Callback destination.
+ * @param pCallbacksSrc Callback source. If set to NULL, the
+ * destination callback table will be unset.
+ */
+void ShClTransferCopyCallbacks(PSHCLTRANSFERCALLBACKTABLE pCallbacksDst,
+ PSHCLTRANSFERCALLBACKTABLE pCallbacksSrc)
+{
+ AssertPtrReturnVoid(pCallbacksDst);
+
+ if (pCallbacksSrc) /* Set */
+ {
+#define SET_CALLBACK(a_pfnCallback) \
+ if (pCallbacksSrc->a_pfnCallback) \
+ pCallbacksDst->a_pfnCallback = pCallbacksSrc->a_pfnCallback
+
+ SET_CALLBACK(pfnOnInitialize);
+ SET_CALLBACK(pfnOnStart);
+ SET_CALLBACK(pfnOnCompleted);
+ SET_CALLBACK(pfnOnError);
+ SET_CALLBACK(pfnOnRegistered);
+ SET_CALLBACK(pfnOnUnregistered);
+
+#undef SET_CALLBACK
+
+ pCallbacksDst->pvUser = pCallbacksSrc->pvUser;
+ pCallbacksDst->cbUser = pCallbacksSrc->cbUser;
+ }
+ else /* Unset */
+ RT_BZERO(pCallbacksDst, sizeof(SHCLTRANSFERCALLBACKTABLE));
+}
+
+/**
+ * Sets or unsets the callback table to be used for a Shared Clipboard transfer.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to set callbacks for.
+ * @param pCallbacks Pointer to callback table to set. If set to NULL,
+ * existing callbacks for this transfer will be unset.
+ */
+void ShClTransferSetCallbacks(PSHCLTRANSFER pTransfer,
+ PSHCLTRANSFERCALLBACKTABLE pCallbacks)
+{
+ AssertPtrReturnVoid(pTransfer);
+ /* pCallbacks can be NULL. */
+
+ ShClTransferCopyCallbacks(&pTransfer->Callbacks, pCallbacks);
+}
+
+/**
+ * Sets the transfer provider interface for a given transfer.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Transfer to create transfer provider for.
+ * @param pCreationCtx Provider creation context to use for provider creation.
+ */
+int ShClTransferSetProviderIface(PSHCLTRANSFER pTransfer,
+ PSHCLTXPROVIDERCREATIONCTX pCreationCtx)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCreationCtx, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ int rc = VINF_SUCCESS;
+
+ pTransfer->ProviderIface = pCreationCtx->Interface;
+ pTransfer->ProviderCtx.pTransfer = pTransfer;
+ pTransfer->ProviderCtx.pvUser = pCreationCtx->pvUser;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Clears (resets) the root list of a Shared Clipboard transfer.
+ *
+ * @param pTransfer Transfer to clear transfer root list for.
+ */
+static void shClTransferListRootsClear(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturnVoid(pTransfer);
+
+ if (pTransfer->pszPathRootAbs)
+ {
+ RTStrFree(pTransfer->pszPathRootAbs);
+ pTransfer->pszPathRootAbs = NULL;
+ }
+
+ PSHCLLISTROOT pListRoot, pListRootNext;
+ RTListForEachSafe(&pTransfer->lstRoots, pListRoot, pListRootNext, SHCLLISTROOT, Node)
+ {
+ RTStrFree(pListRoot->pszPathAbs);
+
+ RTListNodeRemove(&pListRoot->Node);
+
+ RTMemFree(pListRoot);
+ pListRoot = NULL;
+ }
+
+ pTransfer->cRoots = 0;
+}
+
+/**
+ * Resets a Shared Clipboard transfer.
+ *
+ * @param pTransfer Clipboard transfer to reset.
+ */
+void ShClTransferReset(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturnVoid(pTransfer);
+
+ LogFlowFuncEnter();
+
+ shClTransferListRootsClear(pTransfer);
+
+ PSHCLLISTHANDLEINFO pItList, pItListNext;
+ RTListForEachSafe(&pTransfer->lstList, pItList, pItListNext, SHCLLISTHANDLEINFO, Node)
+ {
+ ShClTransferListHandleInfoDestroy(pItList);
+
+ RTListNodeRemove(&pItList->Node);
+
+ RTMemFree(pItList);
+ }
+
+ PSHCLOBJHANDLEINFO pItObj, pItObjNext;
+ RTListForEachSafe(&pTransfer->lstObj, pItObj, pItObjNext, SHCLOBJHANDLEINFO, Node)
+ {
+ ShClTransferObjHandleInfoDestroy(pItObj);
+
+ RTListNodeRemove(&pItObj->Node);
+
+ RTMemFree(pItObj);
+ }
+}
+
+/**
+ * Returns the number of transfer root list entries.
+ *
+ * @returns Root list entry count.
+ * @param pTransfer Clipboard transfer to return root entry count for.
+ */
+uint32_t ShClTransferRootsCount(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pTransfer, 0);
+
+ LogFlowFunc(("[Transfer %RU32] cRoots=%RU64\n", pTransfer->State.uID, pTransfer->cRoots));
+ return (uint32_t)pTransfer->cRoots;
+}
+
+/**
+ * Returns a specific root list entry of a transfer.
+ *
+ * @returns Pointer to root list entry if found, or NULL if not found.
+ * @param pTransfer Clipboard transfer to get root list entry from.
+ * @param uIdx Index of root list entry to return.
+ */
+DECLINLINE(PSHCLLISTROOT) shClTransferRootsGetInternal(PSHCLTRANSFER pTransfer, uint32_t uIdx)
+{
+ if (uIdx >= pTransfer->cRoots)
+ return NULL;
+
+ PSHCLLISTROOT pIt = RTListGetFirst(&pTransfer->lstRoots, SHCLLISTROOT, Node);
+ while (uIdx--) /** @todo Slow, but works for now. */
+ pIt = RTListGetNext(&pTransfer->lstRoots, pIt, SHCLLISTROOT, Node);
+
+ return pIt;
+}
+
+/**
+ * Get a specific root list entry.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to get root list entry of.
+ * @param uIndex Index (zero-based) of entry to get.
+ * @param pEntry Where to store the returned entry on success.
+ */
+int ShClTransferRootsEntry(PSHCLTRANSFER pTransfer,
+ uint64_t uIndex, PSHCLROOTLISTENTRY pEntry)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pEntry, VERR_INVALID_POINTER);
+
+ if (uIndex >= pTransfer->cRoots)
+ return VERR_INVALID_PARAMETER;
+
+ int rc;
+
+ PSHCLLISTROOT pRoot = shClTransferRootsGetInternal(pTransfer, uIndex);
+ AssertPtrReturn(pRoot, VERR_INVALID_PARAMETER);
+
+ /* Make sure that we only advertise relative source paths, not absolute ones. */
+ const char *pcszSrcPath = pRoot->pszPathAbs;
+
+ char *pszFileName = RTPathFilename(pcszSrcPath);
+ if (pszFileName)
+ {
+ Assert(pszFileName >= pcszSrcPath);
+ size_t cchDstBase = pszFileName - pcszSrcPath;
+ const char *pszDstPath = &pcszSrcPath[cchDstBase];
+
+ LogFlowFunc(("pcszSrcPath=%s, pszDstPath=%s\n", pcszSrcPath, pszDstPath));
+
+ rc = ShClTransferListEntryInit(pEntry);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrCopy(pEntry->pszName, pEntry->cbName, pszDstPath);
+ if (RT_SUCCESS(rc))
+ {
+ pEntry->cbInfo = sizeof(SHCLFSOBJINFO);
+ pEntry->pvInfo = (PSHCLFSOBJINFO)RTMemAlloc(pEntry->cbInfo);
+ if (pEntry->pvInfo)
+ {
+ RTFSOBJINFO fsObjInfo;
+ rc = RTPathQueryInfo(pcszSrcPath, &fsObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ ShClFsObjFromIPRT(PSHCLFSOBJINFO(pEntry->pvInfo), &fsObjInfo);
+
+ pEntry->fInfo = VBOX_SHCL_INFO_FLAG_FSOBJINFO;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+ else
+ rc = VERR_INVALID_POINTER;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns the root entries of a Shared Clipboard transfer.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to return root entries for.
+ * @param ppRootList Where to store the root list on success.
+ */
+int ShClTransferRootsGet(PSHCLTRANSFER pTransfer, PSHCLROOTLIST *ppRootList)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppRootList, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ int rc = VINF_SUCCESS;
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLROOTLIST pRootList = ShClTransferRootListAlloc();
+ if (!pRootList)
+ return VERR_NO_MEMORY;
+
+ const uint64_t cRoots = (uint32_t)pTransfer->cRoots;
+
+ LogFlowFunc(("cRoots=%RU64\n", cRoots));
+
+ if (cRoots)
+ {
+ PSHCLROOTLISTENTRY paRootListEntries
+ = (PSHCLROOTLISTENTRY)RTMemAllocZ(cRoots * sizeof(SHCLROOTLISTENTRY));
+ if (paRootListEntries)
+ {
+ for (uint64_t i = 0; i < cRoots; ++i)
+ {
+ rc = ShClTransferRootsEntry(pTransfer, i, &paRootListEntries[i]);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ pRootList->paEntries = paRootListEntries;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ if (RT_SUCCESS(rc))
+ {
+ pRootList->Hdr.cRoots = cRoots;
+ pRootList->Hdr.fRoots = 0; /** @todo Implement this. */
+
+ *ppRootList = pRootList;
+ }
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnRootsGet)
+ rc = pTransfer->ProviderIface.pfnRootsGet(&pTransfer->ProviderCtx, ppRootList);
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sets transfer root list entries for a given transfer.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Transfer to set transfer list entries for.
+ * @param pszRoots String list (separated by CRLF) of root entries to set.
+ * All entries must have the same root path.
+ * @param cbRoots Size (in bytes) of string list.
+ */
+int ShClTransferRootsSet(PSHCLTRANSFER pTransfer, const char *pszRoots, size_t cbRoots)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszRoots, VERR_INVALID_POINTER);
+ AssertReturn(cbRoots, VERR_INVALID_PARAMETER);
+
+ if (!RTStrIsValidEncoding(pszRoots))
+ return VERR_INVALID_UTF8_ENCODING;
+
+ int rc = VINF_SUCCESS;
+
+ shClTransferListRootsClear(pTransfer);
+
+ char *pszPathRootAbs = NULL;
+
+ RTCList<RTCString> lstRootEntries = RTCString(pszRoots, cbRoots - 1).split("\r\n");
+ for (size_t i = 0; i < lstRootEntries.size(); ++i)
+ {
+ PSHCLLISTROOT pListRoot = (PSHCLLISTROOT)RTMemAlloc(sizeof(SHCLLISTROOT));
+ AssertPtrBreakStmt(pListRoot, rc = VERR_NO_MEMORY);
+
+ const char *pszPathCur = RTStrDup(lstRootEntries.at(i).c_str());
+
+ LogFlowFunc(("pszPathCur=%s\n", pszPathCur));
+
+ /* No root path determined yet? */
+ if (!pszPathRootAbs)
+ {
+ pszPathRootAbs = RTStrDup(pszPathCur);
+ if (pszPathRootAbs)
+ {
+ RTPathStripFilename(pszPathRootAbs);
+
+ LogFlowFunc(("pszPathRootAbs=%s\n", pszPathRootAbs));
+
+ /* We don't want to have a relative directory here. */
+ if (RTPathStartsWithRoot(pszPathRootAbs))
+ {
+ rc = shClTransferValidatePath(pszPathRootAbs, true /* Path must exist */);
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ pListRoot->pszPathAbs = RTStrDup(pszPathCur);
+ if (!pListRoot->pszPathAbs)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ RTListAppend(&pTransfer->lstRoots, &pListRoot->Node);
+
+ pTransfer->cRoots++;
+ }
+
+ /* No (valid) root directory found? Bail out early. */
+ if (!pszPathRootAbs)
+ rc = VERR_PATH_NOT_FOUND;
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Step 2:
+ * Go through the created list and make sure all entries have the same root path.
+ */
+ PSHCLLISTROOT pListRoot;
+ RTListForEach(&pTransfer->lstRoots, pListRoot, SHCLLISTROOT, Node)
+ {
+ if (!RTStrStartsWith(pListRoot->pszPathAbs, pszPathRootAbs))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ rc = shClTransferValidatePath(pListRoot->pszPathAbs, true /* Path must exist */);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+
+ /** @todo Entry rollback on failure? */
+
+ if (RT_SUCCESS(rc))
+ {
+ pTransfer->pszPathRootAbs = pszPathRootAbs;
+ LogFlowFunc(("pszPathRootAbs=%s, cRoots=%zu\n", pTransfer->pszPathRootAbs, pTransfer->cRoots));
+
+ LogRel2(("Shared Clipboard: Transfer uses root '%s'\n", pTransfer->pszPathRootAbs));
+ }
+ else
+ {
+ LogRel(("Shared Clipboard: Unable to set roots for transfer, rc=%Rrc\n", rc));
+ RTStrFree(pszPathRootAbs);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns the transfer's ID.
+ *
+ * @returns The transfer's ID.
+ * @param pTransfer Clipboard transfer to return ID for.
+ */
+SHCLTRANSFERID ShClTransferGetID(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pTransfer, 0);
+
+ return pTransfer->State.uID;
+}
+
+/**
+ * Returns the transfer's direction.
+ *
+ * @returns The transfer's direction.
+ * @param pTransfer Clipboard transfer to return direction for.
+ */
+SHCLTRANSFERDIR ShClTransferGetDir(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pTransfer, SHCLTRANSFERDIR_UNKNOWN);
+
+ LogFlowFunc(("[Transfer %RU32] enmDir=%RU32\n", pTransfer->State.uID, pTransfer->State.enmDir));
+ return pTransfer->State.enmDir;
+}
+
+/**
+ * Returns the transfer's source.
+ *
+ * @returns The transfer's source.
+ * @param pTransfer Clipboard transfer to return source for.
+ */
+SHCLSOURCE ShClTransferGetSource(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pTransfer, SHCLSOURCE_INVALID);
+
+ LogFlowFunc(("[Transfer %RU32] enmSource=%RU32\n", pTransfer->State.uID, pTransfer->State.enmSource));
+ return pTransfer->State.enmSource;
+}
+
+/**
+ * Returns the current transfer status.
+ *
+ * @returns Current transfer status.
+ * @param pTransfer Clipboard transfer to return status for.
+ */
+SHCLTRANSFERSTATUS ShClTransferGetStatus(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pTransfer, SHCLTRANSFERSTATUS_NONE);
+
+ LogFlowFunc(("[Transfer %RU32] enmStatus=%RU32\n", pTransfer->State.uID, pTransfer->State.enmStatus));
+ return pTransfer->State.enmStatus;
+}
+
+/**
+ * Runs a started Shared Clipboard transfer in a dedicated thread.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to run.
+ * @param pfnThreadFunc Pointer to thread function to use.
+ * @param pvUser Pointer to user-provided data. Optional.
+ */
+int ShClTransferRun(PSHCLTRANSFER pTransfer, PFNRTTHREAD pfnThreadFunc, void *pvUser)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnThreadFunc, VERR_INVALID_POINTER);
+ /* pvUser is optional. */
+
+ AssertMsgReturn(pTransfer->State.enmStatus == SHCLTRANSFERSTATUS_STARTED,
+ ("Wrong status (currently is %s)\n", ShClTransferStatusToStr(pTransfer->State.enmStatus)),
+ VERR_WRONG_ORDER);
+
+ int rc = shClTransferThreadCreate(pTransfer, pfnThreadFunc, pvUser);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Starts an initialized transfer.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to start.
+ */
+int ShClTransferStart(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ /* Ready to start? */
+ AssertMsgReturn(pTransfer->State.enmStatus == SHCLTRANSFERSTATUS_INITIALIZED,
+ ("Wrong status (currently is %s)\n", ShClTransferStatusToStr(pTransfer->State.enmStatus)),
+ VERR_WRONG_ORDER);
+
+ int rc;
+
+ if (pTransfer->Callbacks.pfnOnStart)
+ {
+ rc = pTransfer->Callbacks.pfnOnStart(&pTransfer->CallbackCtx);
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ pTransfer->State.enmStatus = SHCLTRANSFERSTATUS_STARTED;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Creates a thread for a Shared Clipboard transfer.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to create thread for.
+ * @param pfnThreadFunc Thread function to use for this transfer.
+ * @param pvUser Pointer to user-provided data.
+ */
+static int shClTransferThreadCreate(PSHCLTRANSFER pTransfer, PFNRTTHREAD pfnThreadFunc, void *pvUser)
+
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ /* Already marked for stopping? */
+ AssertMsgReturn(pTransfer->Thread.fStop == false,
+ ("Transfer thread already marked for stopping"), VERR_WRONG_ORDER);
+ /* Already started? */
+ AssertMsgReturn(pTransfer->Thread.fStarted == false,
+ ("Transfer thread already started"), VERR_WRONG_ORDER);
+
+ /* Spawn a worker thread, so that we don't block the window thread for too long. */
+ int rc = RTThreadCreate(&pTransfer->Thread.hThread, pfnThreadFunc,
+ pvUser, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE,
+ "shclp");
+ if (RT_SUCCESS(rc))
+ {
+ int rc2 = RTThreadUserWait(pTransfer->Thread.hThread, 30 * 1000 /* Timeout in ms */);
+ AssertRC(rc2);
+
+ if (pTransfer->Thread.fStarted) /* Did the thread indicate that it started correctly? */
+ {
+ /* Nothing to do in here. */
+ }
+ else
+ rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Destroys a thread of a Shared Clipboard transfer.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to destroy thread for.
+ * @param uTimeoutMs Timeout (in ms) to wait for thread creation.
+ */
+static int shClTransferThreadDestroy(PSHCLTRANSFER pTransfer, RTMSINTERVAL uTimeoutMs)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ if (pTransfer->Thread.hThread == NIL_RTTHREAD)
+ return VINF_SUCCESS;
+
+ LogFlowFuncEnter();
+
+ /* Set stop indicator. */
+ pTransfer->Thread.fStop = true;
+
+ int rcThread = VERR_WRONG_ORDER;
+ int rc = RTThreadWait(pTransfer->Thread.hThread, uTimeoutMs, &rcThread);
+
+ LogFlowFunc(("Waiting for thread resulted in %Rrc (thread exited with %Rrc)\n", rc, rcThread));
+
+ return rc;
+}
+
+/**
+ * Initializes a Shared Clipboard transfer context.
+ *
+ * @returns VBox status code.
+ * @param pTransferCtx Transfer context to initialize.
+ */
+int ShClTransferCtxInit(PSHCLTRANSFERCTX pTransferCtx)
+{
+ AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pTransferCtx=%p\n", pTransferCtx));
+
+ int rc = RTCritSectInit(&pTransferCtx->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ RTListInit(&pTransferCtx->List);
+
+ pTransferCtx->cTransfers = 0;
+ pTransferCtx->cRunning = 0;
+ pTransferCtx->cMaxRunning = 64; /** @todo Make this configurable? */
+
+ RT_ZERO(pTransferCtx->bmTransferIds);
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
+ ShClTransferHttpServerInit(&pTransferCtx->HttpServer);
+#endif
+ ShClTransferCtxReset(pTransferCtx);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a Shared Clipboard transfer context struct.
+ *
+ * @param pTransferCtx Transfer context to destroy.
+ */
+void ShClTransferCtxDestroy(PSHCLTRANSFERCTX pTransferCtx)
+{
+ if (!pTransferCtx)
+ return;
+
+ LogFlowFunc(("pTransferCtx=%p\n", pTransferCtx));
+
+ if (RTCritSectIsInitialized(&pTransferCtx->CritSect))
+ RTCritSectDelete(&pTransferCtx->CritSect);
+
+ PSHCLTRANSFER pTransfer, pTransferNext;
+ RTListForEachSafe(&pTransferCtx->List, pTransfer, pTransferNext, SHCLTRANSFER, Node)
+ {
+ ShClTransferDestroy(pTransfer);
+
+ shclTransferCtxTransferRemoveAndUnregister(pTransferCtx, pTransfer);
+
+ RTMemFree(pTransfer);
+ pTransfer = NULL;
+ }
+
+ pTransferCtx->cRunning = 0;
+ pTransferCtx->cTransfers = 0;
+}
+
+/**
+ * Resets a Shared Clipboard transfer.
+ *
+ * @param pTransferCtx Transfer context to reset.
+ */
+void ShClTransferCtxReset(PSHCLTRANSFERCTX pTransferCtx)
+{
+ AssertPtrReturnVoid(pTransferCtx);
+
+ LogFlowFuncEnter();
+
+ PSHCLTRANSFER pTransfer;
+ RTListForEach(&pTransferCtx->List, pTransfer, SHCLTRANSFER, Node)
+ ShClTransferReset(pTransfer);
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
+ /** @todo Anything to do here? */
+#endif
+}
+
+/**
+ * Returns a specific Shared Clipboard transfer, internal version.
+ *
+ * @returns Shared Clipboard transfer, or NULL if not found.
+ * @param pTransferCtx Transfer context to return transfer for.
+ * @param uID ID of the transfer to return.
+ */
+static PSHCLTRANSFER shClTransferCtxGetTransferByIdInternal(PSHCLTRANSFERCTX pTransferCtx, uint32_t uID)
+{
+ PSHCLTRANSFER pTransfer;
+ RTListForEach(&pTransferCtx->List, pTransfer, SHCLTRANSFER, Node) /** @todo Slow, but works for now. */
+ {
+ if (pTransfer->State.uID == uID)
+ return pTransfer;
+ }
+
+ return NULL;
+}
+
+/**
+ * Returns a specific Shared Clipboard transfer by index, internal version.
+ *
+ * @returns Shared Clipboard transfer, or NULL if not found.
+ * @param pTransferCtx Transfer context to return transfer for.
+ * @param uIdx Index of the transfer to return.
+ */
+static PSHCLTRANSFER shClTransferCtxGetTransferByIndexInternal(PSHCLTRANSFERCTX pTransferCtx, uint32_t uIdx)
+{
+ uint32_t idx = 0;
+
+ PSHCLTRANSFER pTransfer;
+ RTListForEach(&pTransferCtx->List, pTransfer, SHCLTRANSFER, Node) /** @todo Slow, but works for now. */
+ {
+ if (uIdx == idx)
+ return pTransfer;
+ idx++;
+ }
+
+ return NULL;
+}
+
+/**
+ * Returns a Shared Clipboard transfer for a specific transfer ID.
+ *
+ * @returns Shared Clipboard transfer, or NULL if not found.
+ * @param pTransferCtx Transfer context to return transfer for.
+ * @param uID ID of the transfer to return.
+ */
+PSHCLTRANSFER ShClTransferCtxGetTransferById(PSHCLTRANSFERCTX pTransferCtx, uint32_t uID)
+{
+ return shClTransferCtxGetTransferByIdInternal(pTransferCtx, uID);
+}
+
+/**
+ * Returns a Shared Clipboard transfer for a specific list index.
+ *
+ * @returns Shared Clipboard transfer, or NULL if not found.
+ * @param pTransferCtx Transfer context to return transfer for.
+ * @param uIdx List index of the transfer to return.
+ */
+PSHCLTRANSFER ShClTransferCtxGetTransferByIndex(PSHCLTRANSFERCTX pTransferCtx, uint32_t uIdx)
+{
+ return shClTransferCtxGetTransferByIndexInternal(pTransferCtx, uIdx);
+}
+
+/**
+ * Returns the number of running Shared Clipboard transfers.
+ *
+ * @returns Number of running transfers.
+ * @param pTransferCtx Transfer context to return number for.
+ */
+uint32_t ShClTransferCtxGetRunningTransfers(PSHCLTRANSFERCTX pTransferCtx)
+{
+ AssertPtrReturn(pTransferCtx, 0);
+ return pTransferCtx->cRunning;
+}
+
+/**
+ * Returns the number of total Shared Clipboard transfers.
+ *
+ * @returns Number of total transfers.
+ * @param pTransferCtx Transfer context to return number for.
+ */
+uint32_t ShClTransferCtxGetTotalTransfers(PSHCLTRANSFERCTX pTransferCtx)
+{
+ AssertPtrReturn(pTransferCtx, 0);
+ return pTransferCtx->cTransfers;
+}
+
+/**
+ * Registers a Shared Clipboard transfer with a transfer context, i.e. allocates a transfer ID.
+ *
+ * @return VBox status code.
+ * @retval VERR_SHCLPB_MAX_TRANSFERS_REACHED if the maximum of concurrent transfers
+ * is reached.
+ * @param pTransferCtx Transfer context to register transfer to.
+ * @param pTransfer Transfer to register.
+ * @param pidTransfer Where to return the transfer ID on success. Optional.
+ */
+int ShClTransferCtxTransferRegister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer, SHCLTRANSFERID *pidTransfer)
+{
+ AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ /* pidTransfer is optional. */
+
+ /*
+ * Pick a random bit as starting point. If it's in use, search forward
+ * for a free one, wrapping around. We've reserved both the zero'th and
+ * max-1 IDs.
+ */
+ SHCLTRANSFERID idTransfer = RTRandU32Ex(1, VBOX_SHCL_MAX_TRANSFERS - 2);
+
+ if (!ASMBitTestAndSet(&pTransferCtx->bmTransferIds[0], idTransfer))
+ { /* likely */ }
+ else if (pTransferCtx->cTransfers < VBOX_SHCL_MAX_TRANSFERS - 2 /* First and last are not used */)
+ {
+ /* Forward search. */
+ int iHit = ASMBitNextClear(&pTransferCtx->bmTransferIds[0], VBOX_SHCL_MAX_TRANSFERS, idTransfer);
+ if (iHit < 0)
+ iHit = ASMBitFirstClear(&pTransferCtx->bmTransferIds[0], VBOX_SHCL_MAX_TRANSFERS);
+ AssertLogRelMsgReturn(iHit >= 0, ("Transfer count: %RU16\n", pTransferCtx->cTransfers), VERR_SHCLPB_MAX_TRANSFERS_REACHED);
+ idTransfer = iHit;
+ AssertLogRelMsgReturn(!ASMBitTestAndSet(&pTransferCtx->bmTransferIds[0], idTransfer), ("idObject=%#x\n", idTransfer), VERR_INTERNAL_ERROR_2);
+ }
+ else
+ {
+ LogFunc(("Maximum number of transfers reached (%RU16 transfers)\n", pTransferCtx->cTransfers));
+ return VERR_SHCLPB_MAX_TRANSFERS_REACHED;
+ }
+
+ Log2Func(("pTransfer=%p, idTransfer=%RU32 (%RU16 transfers)\n", pTransfer, idTransfer, pTransferCtx->cTransfers));
+
+ pTransfer->State.uID = idTransfer;
+
+ RTListAppend(&pTransferCtx->List, &pTransfer->Node);
+
+ pTransferCtx->cTransfers++;
+
+ if (pTransfer->Callbacks.pfnOnRegistered)
+ pTransfer->Callbacks.pfnOnRegistered(&pTransfer->CallbackCtx, pTransferCtx);
+
+ if (pidTransfer)
+ *pidTransfer = idTransfer;
+
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Registers a Shared Clipboard transfer with a transfer context by specifying an ID for the transfer.
+ *
+ * @return VBox status code.
+ * @retval VERR_ALREADY_EXISTS if a transfer with the given ID already exists.
+ * @retval VERR_SHCLPB_MAX_TRANSFERS_REACHED if the maximum of concurrent transfers for this context has been reached.
+ * @param pTransferCtx Transfer context to register transfer to.
+ * @param pTransfer Transfer to register.
+ * @param idTransfer Transfer ID to use for registration.
+ */
+int ShClTransferCtxTransferRegisterById(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer, SHCLTRANSFERID idTransfer)
+{
+ LogFlowFunc(("cTransfers=%RU16, idTransfer=%RU32\n", pTransferCtx->cTransfers, idTransfer));
+
+ if (pTransferCtx->cTransfers < VBOX_SHCL_MAX_TRANSFERS - 2 /* First and last are not used */)
+ {
+ if (!ASMBitTestAndSet(&pTransferCtx->bmTransferIds[0], idTransfer))
+ {
+ RTListAppend(&pTransferCtx->List, &pTransfer->Node);
+
+ pTransfer->State.uID = idTransfer;
+
+ if (pTransfer->Callbacks.pfnOnRegistered)
+ pTransfer->Callbacks.pfnOnRegistered(&pTransfer->CallbackCtx, pTransferCtx);
+
+ pTransferCtx->cTransfers++;
+ return VINF_SUCCESS;
+ }
+
+ return VERR_ALREADY_EXISTS;
+ }
+
+ LogFunc(("Maximum number of transfers reached (%RU16 transfers)\n", pTransferCtx->cTransfers));
+ return VERR_SHCLPB_MAX_TRANSFERS_REACHED;
+}
+
+/**
+ * Removes and unregisters a transfer from a transfer context.
+ *
+ * @param pTransferCtx Transfer context to remove transfer from.
+ * @param pTransfer Transfer to remove.
+ */
+static void shclTransferCtxTransferRemoveAndUnregister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer)
+{
+ RTListNodeRemove(&pTransfer->Node);
+
+ Assert(pTransferCtx->cTransfers);
+ pTransferCtx->cTransfers--;
+
+ Assert(pTransferCtx->cTransfers >= pTransferCtx->cRunning);
+
+ if (pTransfer->Callbacks.pfnOnUnregistered)
+ pTransfer->Callbacks.pfnOnUnregistered(&pTransfer->CallbackCtx, pTransferCtx);
+
+ LogFlowFunc(("Now %RU32 transfers left\n", pTransferCtx->cTransfers));
+}
+
+/**
+ * Unregisters a transfer from an Transfer context.
+ *
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_NOT_FOUND if the transfer ID was not found.
+ * @param pTransferCtx Transfer context to unregister transfer from.
+ * @param idTransfer Transfer ID to unregister.
+ */
+int ShClTransferCtxTransferUnregister(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANSFERID idTransfer)
+{
+ int rc = VINF_SUCCESS;
+ AssertMsgStmt(ASMBitTestAndClear(&pTransferCtx->bmTransferIds, idTransfer), ("idTransfer=%#x\n", idTransfer), rc = VERR_NOT_FOUND);
+
+ LogFlowFunc(("idTransfer=%RU32\n", idTransfer));
+
+ PSHCLTRANSFER pTransfer = shClTransferCtxGetTransferByIdInternal(pTransferCtx, idTransfer);
+ if (pTransfer)
+ {
+ shclTransferCtxTransferRemoveAndUnregister(pTransferCtx, pTransfer);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Cleans up all associated transfers which are not needed (anymore).
+ * This can be due to transfers which only have been announced but not / never being run.
+ *
+ * @param pTransferCtx Transfer context to cleanup transfers for.
+ */
+void ShClTransferCtxCleanup(PSHCLTRANSFERCTX pTransferCtx)
+{
+ AssertPtrReturnVoid(pTransferCtx);
+
+ LogFlowFunc(("pTransferCtx=%p, cTransfers=%RU16 cRunning=%RU16\n",
+ pTransferCtx, pTransferCtx->cTransfers, pTransferCtx->cRunning));
+
+ if (pTransferCtx->cTransfers == 0)
+ return;
+
+ /* Remove all transfers which are not in a running state (e.g. only announced). */
+ PSHCLTRANSFER pTransfer, pTransferNext;
+ RTListForEachSafe(&pTransferCtx->List, pTransfer, pTransferNext, SHCLTRANSFER, Node)
+ {
+ if (ShClTransferGetStatus(pTransfer) != SHCLTRANSFERSTATUS_STARTED)
+ {
+ shclTransferCtxTransferRemoveAndUnregister(pTransferCtx, pTransfer);
+
+ ShClTransferDestroy(pTransfer);
+
+ RTMemFree(pTransfer);
+ pTransfer = NULL;
+ }
+ }
+}
+
+/**
+ * Returns whether the maximum of concurrent transfers of a specific transfer contexthas been reached or not.
+ *
+ * @returns \c if maximum has been reached, \c false if not.
+ * @param pTransferCtx Transfer context to determine value for.
+ */
+bool ShClTransferCtxTransfersMaximumReached(PSHCLTRANSFERCTX pTransferCtx)
+{
+ AssertPtrReturn(pTransferCtx, true);
+
+ LogFlowFunc(("cRunning=%RU32, cMaxRunning=%RU32\n", pTransferCtx->cRunning, pTransferCtx->cMaxRunning));
+
+ Assert(pTransferCtx->cRunning <= pTransferCtx->cMaxRunning);
+ return pTransferCtx->cRunning == pTransferCtx->cMaxRunning;
+}
+
+/**
+ * Copies file system objinfo from IPRT to Shared Clipboard format.
+ *
+ * @param pDst The Shared Clipboard structure to convert data to.
+ * @param pSrc The IPRT structure to convert data from.
+ */
+void ShClFsObjFromIPRT(PSHCLFSOBJINFO pDst, PCRTFSOBJINFO pSrc)
+{
+ pDst->cbObject = pSrc->cbObject;
+ pDst->cbAllocated = pSrc->cbAllocated;
+ pDst->AccessTime = pSrc->AccessTime;
+ pDst->ModificationTime = pSrc->ModificationTime;
+ pDst->ChangeTime = pSrc->ChangeTime;
+ pDst->BirthTime = pSrc->BirthTime;
+ pDst->Attr.fMode = pSrc->Attr.fMode;
+ /* Clear bits which we don't pass through for security reasons. */
+ pDst->Attr.fMode &= ~(RTFS_UNIX_ISUID | RTFS_UNIX_ISGID | RTFS_UNIX_ISTXT);
+ RT_ZERO(pDst->Attr.u);
+ switch (pSrc->Attr.enmAdditional)
+ {
+ default:
+ case RTFSOBJATTRADD_NOTHING:
+ pDst->Attr.enmAdditional = SHCLFSOBJATTRADD_NOTHING;
+ break;
+
+ case RTFSOBJATTRADD_UNIX:
+ pDst->Attr.enmAdditional = SHCLFSOBJATTRADD_UNIX;
+ pDst->Attr.u.Unix.uid = pSrc->Attr.u.Unix.uid;
+ pDst->Attr.u.Unix.gid = pSrc->Attr.u.Unix.gid;
+ pDst->Attr.u.Unix.cHardlinks = pSrc->Attr.u.Unix.cHardlinks;
+ pDst->Attr.u.Unix.INodeIdDevice = pSrc->Attr.u.Unix.INodeIdDevice;
+ pDst->Attr.u.Unix.INodeId = pSrc->Attr.u.Unix.INodeId;
+ pDst->Attr.u.Unix.fFlags = pSrc->Attr.u.Unix.fFlags;
+ pDst->Attr.u.Unix.GenerationId = pSrc->Attr.u.Unix.GenerationId;
+ pDst->Attr.u.Unix.Device = pSrc->Attr.u.Unix.Device;
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ pDst->Attr.enmAdditional = SHCLFSOBJATTRADD_EASIZE;
+ pDst->Attr.u.EASize.cb = pSrc->Attr.u.EASize.cb;
+ break;
+ }
+}
+
+/**
+ * Converts Shared Clipboard create flags (see SharedClipboard-transfers.h) into IPRT create flags.
+ *
+ * @returns IPRT status code.
+ * @param fShClFlags Shared clipboard create flags.
+ * @param[out] pfOpen Where to store the RTFILE_O_XXX flags for
+ * RTFileOpen.
+ *
+ * @sa Initially taken from vbsfConvertFileOpenFlags().
+ */
+static int shClConvertFileCreateFlags(uint32_t fShClFlags, uint64_t *pfOpen)
+{
+ AssertMsgReturnStmt(!(fShClFlags & ~SHCL_OBJ_CF_VALID_MASK), ("%#x4\n", fShClFlags), *pfOpen = 0, VERR_INVALID_FLAGS);
+
+ uint64_t fOpen = 0;
+
+ switch (fShClFlags & SHCL_OBJ_CF_ACCESS_MASK_RW)
+ {
+ case SHCL_OBJ_CF_ACCESS_NONE:
+ {
+#ifdef RT_OS_WINDOWS
+ if ((fShClFlags & SHCL_OBJ_CF_ACCESS_MASK_ATTR) != SHCL_OBJ_CF_ACCESS_ATTR_NONE)
+ fOpen |= RTFILE_O_OPEN | RTFILE_O_ATTR_ONLY;
+ else
+#endif
+ fOpen |= RTFILE_O_OPEN | RTFILE_O_READ;
+ LogFlowFunc(("SHCL_OBJ_CF_ACCESS_NONE\n"));
+ break;
+ }
+
+ case SHCL_OBJ_CF_ACCESS_READ:
+ {
+ fOpen |= RTFILE_O_OPEN | RTFILE_O_READ;
+ LogFlowFunc(("SHCL_OBJ_CF_ACCESS_READ\n"));
+ break;
+ }
+
+ default:
+ AssertFailedReturn(VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+
+ switch (fShClFlags & SHCL_OBJ_CF_ACCESS_MASK_ATTR)
+ {
+ case SHCL_OBJ_CF_ACCESS_ATTR_NONE:
+ {
+ fOpen |= RTFILE_O_ACCESS_ATTR_DEFAULT;
+ LogFlowFunc(("SHCL_OBJ_CF_ACCESS_ATTR_NONE\n"));
+ break;
+ }
+
+ case SHCL_OBJ_CF_ACCESS_ATTR_READ:
+ {
+ fOpen |= RTFILE_O_ACCESS_ATTR_READ;
+ LogFlowFunc(("SHCL_OBJ_CF_ACCESS_ATTR_READ\n"));
+ break;
+ }
+
+ default:
+ AssertFailedReturn(VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+
+ /* Sharing mask */
+ switch (fShClFlags & SHCL_OBJ_CF_ACCESS_MASK_DENY)
+ {
+ case SHCL_OBJ_CF_ACCESS_DENYNONE:
+ fOpen |= RTFILE_O_DENY_NONE;
+ LogFlowFunc(("SHCL_OBJ_CF_ACCESS_DENYNONE\n"));
+ break;
+
+ case SHCL_OBJ_CF_ACCESS_DENYWRITE:
+ fOpen |= RTFILE_O_DENY_WRITE;
+ LogFlowFunc(("SHCL_OBJ_CF_ACCESS_DENYWRITE\n"));
+ break;
+
+ default:
+ AssertFailedReturn(VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+
+ *pfOpen = fOpen;
+
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Translates a Shared Clipboard transfer status (SHCLTRANSFERSTATUS_XXX) into a string.
+ *
+ * @returns Transfer status string name.
+ * @param enmStatus The transfer status to translate.
+ */
+const char *ShClTransferStatusToStr(SHCLTRANSFERSTATUS enmStatus)
+{
+ switch (enmStatus)
+ {
+ RT_CASE_RET_STR(SHCLTRANSFERSTATUS_NONE);
+ RT_CASE_RET_STR(SHCLTRANSFERSTATUS_INITIALIZED);
+ RT_CASE_RET_STR(SHCLTRANSFERSTATUS_STARTED);
+ RT_CASE_RET_STR(SHCLTRANSFERSTATUS_STOPPED);
+ RT_CASE_RET_STR(SHCLTRANSFERSTATUS_CANCELED);
+ RT_CASE_RET_STR(SHCLTRANSFERSTATUS_KILLED);
+ RT_CASE_RET_STR(SHCLTRANSFERSTATUS_ERROR);
+ }
+ return "Unknown";
+}
diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp
new file mode 100644
index 00000000..cc94eb28
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp
@@ -0,0 +1,1322 @@
+/* $Id: clipboard-win.cpp $ */
+/** @file
+ * Shared Clipboard: Windows-specific functions for clipboard handling.
+ */
+
+/*
+ * Copyright (C) 2006-2022 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
+ */
+
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <VBox/GuestHost/SharedClipboard.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/ldr.h>
+#include <iprt/mem.h>
+#include <iprt/thread.h>
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+# include <iprt/win/windows.h>
+# include <iprt/win/shlobj.h> /* For CFSTR_FILEDESCRIPTORXXX + CFSTR_FILECONTENTS. */
+# include <iprt/utf16.h>
+#endif
+
+#include <VBox/log.h>
+
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+# include <VBox/GuestHost/SharedClipboard-transfers.h>
+#endif
+#include <VBox/GuestHost/SharedClipboard-win.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+
+
+/**
+ * Opens the clipboard of a specific window.
+ *
+ * @returns VBox status code.
+ * @param hWnd Handle of window to open clipboard for.
+ */
+int SharedClipboardWinOpen(HWND hWnd)
+{
+ /* "OpenClipboard fails if another window has the clipboard open."
+ * So try a few times and wait up to 1 second.
+ */
+ BOOL fOpened = FALSE;
+
+ LogFlowFunc(("hWnd=%p\n", hWnd));
+
+ int i = 0;
+ for (;;)
+ {
+ if (OpenClipboard(hWnd))
+ {
+ fOpened = TRUE;
+ break;
+ }
+
+ if (i >= 10) /* sleep interval = [1..512] ms */
+ break;
+
+ RTThreadSleep(1 << i);
+ ++i;
+ }
+
+#ifdef LOG_ENABLED
+ if (i > 0)
+ LogFlowFunc(("%d times tried to open clipboard\n", i + 1));
+#endif
+
+ int rc;
+ if (fOpened)
+ rc = VINF_SUCCESS;
+ else
+ {
+ const DWORD dwLastErr = GetLastError();
+ rc = RTErrConvertFromWin32(dwLastErr);
+ LogRel(("Failed to open clipboard, rc=%Rrc (0x%x)\n", rc, dwLastErr));
+ }
+
+ return rc;
+}
+
+/**
+ * Closes the clipboard for the current thread.
+ *
+ * @returns VBox status code.
+ */
+int SharedClipboardWinClose(void)
+{
+ int rc;
+
+ const BOOL fRc = CloseClipboard();
+ if (RT_UNLIKELY(!fRc))
+ {
+ const DWORD dwLastErr = GetLastError();
+ if (dwLastErr == ERROR_CLIPBOARD_NOT_OPEN)
+ {
+ rc = VINF_SUCCESS; /* Not important, so just report success instead. */
+ }
+ else
+ {
+ rc = RTErrConvertFromWin32(dwLastErr);
+ LogFunc(("Failed with %Rrc (0x%x)\n", rc, dwLastErr));
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Clears the clipboard for the current thread.
+ *
+ * @returns VBox status code.
+ */
+int SharedClipboardWinClear(void)
+{
+ LogFlowFuncEnter();
+ if (EmptyClipboard())
+ return VINF_SUCCESS;
+
+ const DWORD dwLastErr = GetLastError();
+ AssertReturn(dwLastErr != ERROR_CLIPBOARD_NOT_OPEN, VERR_INVALID_STATE);
+
+ int rc = RTErrConvertFromWin32(dwLastErr);
+ LogFunc(("Failed with %Rrc (0x%x)\n", rc, dwLastErr));
+ return rc;
+}
+
+/**
+ * Initializes a Shared Clipboard Windows context.
+ *
+ * @returns VBox status code.
+ * @param pWinCtx Shared Clipboard Windows context to initialize.
+ */
+int SharedClipboardWinCtxInit(PSHCLWINCTX pWinCtx)
+{
+ int rc = RTCritSectInit(&pWinCtx->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check that new Clipboard API is available. */
+ SharedClipboardWinCheckAndInitNewAPI(&pWinCtx->newAPI);
+ /* Do *not* check the rc, as the call might return VERR_SYMBOL_NOT_FOUND is the new API isn't available. */
+
+ pWinCtx->hWnd = NULL;
+ pWinCtx->hWndClipboardOwnerUs = NULL;
+ pWinCtx->hWndNextInChain = NULL;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Destroys a Shared Clipboard Windows context.
+ *
+ * @param pWinCtx Shared Clipboard Windows context to destroy.
+ */
+void SharedClipboardWinCtxDestroy(PSHCLWINCTX pWinCtx)
+{
+ if (!pWinCtx)
+ return;
+
+ LogFlowFuncEnter();
+
+ if (RTCritSectIsInitialized(&pWinCtx->CritSect))
+ {
+ int rc2 = RTCritSectDelete(&pWinCtx->CritSect);
+ AssertRC(rc2);
+ }
+}
+
+/**
+ * Checks and initializes function pointer which are required for using
+ * the new clipboard API.
+ *
+ * @returns VBox status code, or VERR_SYMBOL_NOT_FOUND if the new API is not available.
+ * @param pAPI Where to store the retrieved function pointers.
+ * Will be set to NULL if the new API is not available.
+ */
+int SharedClipboardWinCheckAndInitNewAPI(PSHCLWINAPINEW pAPI)
+{
+ RTLDRMOD hUser32 = NIL_RTLDRMOD;
+ int rc = RTLdrLoadSystem("User32.dll", /* fNoUnload = */ true, &hUser32);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hUser32, "AddClipboardFormatListener", (void **)&pAPI->pfnAddClipboardFormatListener);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hUser32, "RemoveClipboardFormatListener", (void **)&pAPI->pfnRemoveClipboardFormatListener);
+ }
+
+ RTLdrClose(hUser32);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ LogRel(("Shared Clipboard: New Clipboard API enabled\n"));
+ }
+ else
+ {
+ RT_BZERO(pAPI, sizeof(SHCLWINAPINEW));
+ LogRel(("Shared Clipboard: New Clipboard API not available (%Rrc)\n", rc));
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns if the new clipboard API is available or not.
+ *
+ * @returns @c true if the new API is available, or @c false if not.
+ * @param pAPI Structure used for checking if the new clipboard API is available or not.
+ */
+bool SharedClipboardWinIsNewAPI(PSHCLWINAPINEW pAPI)
+{
+ if (!pAPI)
+ return false;
+ return pAPI->pfnAddClipboardFormatListener != NULL;
+}
+
+/**
+ * Adds ourselves into the chain of cliboard listeners.
+ *
+ * @returns VBox status code.
+ * @param pCtx Windows clipboard context to use to add ourselves.
+ */
+int SharedClipboardWinChainAdd(PSHCLWINCTX pCtx)
+{
+ const PSHCLWINAPINEW pAPI = &pCtx->newAPI;
+
+ BOOL fRc;
+ if (SharedClipboardWinIsNewAPI(pAPI))
+ fRc = pAPI->pfnAddClipboardFormatListener(pCtx->hWnd);
+ else
+ {
+ SetLastError(NO_ERROR);
+ pCtx->hWndNextInChain = SetClipboardViewer(pCtx->hWnd);
+ fRc = pCtx->hWndNextInChain != NULL || GetLastError() == NO_ERROR;
+ }
+
+ int rc = VINF_SUCCESS;
+
+ if (!fRc)
+ {
+ const DWORD dwLastErr = GetLastError();
+ rc = RTErrConvertFromWin32(dwLastErr);
+ LogFunc(("Failed with %Rrc (0x%x)\n", rc, dwLastErr));
+ }
+
+ return rc;
+}
+
+/**
+ * Remove ourselves from the chain of cliboard listeners
+ *
+ * @returns VBox status code.
+ * @param pCtx Windows clipboard context to use to remove ourselves.
+ */
+int SharedClipboardWinChainRemove(PSHCLWINCTX pCtx)
+{
+ if (!pCtx->hWnd)
+ return VINF_SUCCESS;
+
+ const PSHCLWINAPINEW pAPI = &pCtx->newAPI;
+
+ BOOL fRc;
+ if (SharedClipboardWinIsNewAPI(pAPI))
+ {
+ fRc = pAPI->pfnRemoveClipboardFormatListener(pCtx->hWnd);
+ }
+ else
+ {
+ fRc = ChangeClipboardChain(pCtx->hWnd, pCtx->hWndNextInChain);
+ if (fRc)
+ pCtx->hWndNextInChain = NULL;
+ }
+
+ int rc = VINF_SUCCESS;
+
+ if (!fRc)
+ {
+ const DWORD dwLastErr = GetLastError();
+ rc = RTErrConvertFromWin32(dwLastErr);
+ LogFunc(("Failed with %Rrc (0x%x)\n", rc, dwLastErr));
+ }
+
+ return rc;
+}
+
+/**
+ * Callback which is invoked when we have successfully pinged ourselves down the
+ * clipboard chain. We simply unset a boolean flag to say that we are responding.
+ * There is a race if a ping returns after the next one is initiated, but nothing
+ * very bad is likely to happen.
+ *
+ * @param hWnd Window handle to use for this callback. Not used currently.
+ * @param uMsg Message to handle. Not used currently.
+ * @param dwData Pointer to user-provided data. Contains our Windows clipboard context.
+ * @param lResult Additional data to pass. Not used currently.
+ */
+VOID CALLBACK SharedClipboardWinChainPingProc(HWND hWnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult) RT_NOTHROW_DEF
+{
+ RT_NOREF(hWnd);
+ RT_NOREF(uMsg);
+ RT_NOREF(lResult);
+
+ /** @todo r=andy Why not using SetWindowLongPtr for keeping the context? */
+ PSHCLWINCTX pCtx = (PSHCLWINCTX)dwData;
+ AssertPtrReturnVoid(pCtx);
+
+ pCtx->oldAPI.fCBChainPingInProcess = FALSE;
+}
+
+/**
+ * Passes a window message to the next window in the clipboard chain.
+ *
+ * @returns LRESULT
+ * @param pWinCtx Window context to use.
+ * @param msg Window message to pass.
+ * @param wParam WPARAM to pass.
+ * @param lParam LPARAM to pass.
+ */
+LRESULT SharedClipboardWinChainPassToNext(PSHCLWINCTX pWinCtx,
+ UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ LogFlowFuncEnter();
+
+ LRESULT lresultRc = 0;
+
+ if (pWinCtx->hWndNextInChain)
+ {
+ LogFunc(("hWndNextInChain=%p\n", pWinCtx->hWndNextInChain));
+
+ /* Pass the message to next window in the clipboard chain. */
+ DWORD_PTR dwResult;
+ lresultRc = SendMessageTimeout(pWinCtx->hWndNextInChain, msg, wParam, lParam, 0,
+ SHCL_WIN_CBCHAIN_TIMEOUT_MS, &dwResult);
+ if (!lresultRc)
+ lresultRc = dwResult;
+ }
+
+ LogFlowFunc(("lresultRc=%ld\n", lresultRc));
+ return lresultRc;
+}
+
+/**
+ * Converts a (registered or standard) Windows clipboard format to a VBox clipboard format.
+ *
+ * @returns Converted VBox clipboard format, or VBOX_SHCL_FMT_NONE if not found.
+ * @param uFormat Windows clipboard format to convert.
+ */
+SHCLFORMAT SharedClipboardWinClipboardFormatToVBox(UINT uFormat)
+{
+ /* Insert the requested clipboard format data into the clipboard. */
+ SHCLFORMAT vboxFormat = VBOX_SHCL_FMT_NONE;
+
+ switch (uFormat)
+ {
+ case CF_UNICODETEXT:
+ vboxFormat = VBOX_SHCL_FMT_UNICODETEXT;
+ break;
+
+ case CF_DIB:
+ vboxFormat = VBOX_SHCL_FMT_BITMAP;
+ break;
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ /* CF_HDROP handles file system entries which are locally present
+ * on source for transferring to the target.
+ *
+ * This does *not* invoke any IDataObject / IStream implementations! */
+ case CF_HDROP:
+ vboxFormat = VBOX_SHCL_FMT_URI_LIST;
+ break;
+#endif
+
+ default:
+ if (uFormat >= 0xC000) /** Formats registered with RegisterClipboardFormat() start at this index. */
+ {
+ TCHAR szFormatName[256]; /** @todo r=andy Do we need Unicode support here as well? */
+ int cActual = GetClipboardFormatName(uFormat, szFormatName, sizeof(szFormatName) / sizeof(TCHAR));
+ if (cActual)
+ {
+ LogFlowFunc(("uFormat=%u -> szFormatName=%s\n", uFormat, szFormatName));
+
+ if (RTStrCmp(szFormatName, SHCL_WIN_REGFMT_HTML) == 0)
+ vboxFormat = VBOX_SHCL_FMT_HTML;
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ /* These types invoke our IDataObject / IStream implementations. */
+ else if ( (RTStrCmp(szFormatName, CFSTR_FILEDESCRIPTORA) == 0)
+ || (RTStrCmp(szFormatName, CFSTR_FILECONTENTS) == 0))
+ vboxFormat = VBOX_SHCL_FMT_URI_LIST;
+ /** @todo Do we need to handle CFSTR_FILEDESCRIPTORW here as well? */
+#endif
+ }
+ }
+ break;
+ }
+
+ LogFlowFunc(("uFormat=%u -> vboxFormat=0x%x\n", uFormat, vboxFormat));
+ return vboxFormat;
+}
+
+/**
+ * Retrieves all supported clipboard formats of a specific clipboard.
+ *
+ * @returns VBox status code.
+ * @param pCtx Windows clipboard context to retrieve formats for.
+ * @param pfFormats Where to store the retrieved formats.
+ */
+int SharedClipboardWinGetFormats(PSHCLWINCTX pCtx, PSHCLFORMATS pfFormats)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfFormats, VERR_INVALID_POINTER);
+
+ SHCLFORMATS fFormats = VBOX_SHCL_FMT_NONE;
+
+ /* Query list of available formats and report to host. */
+ int rc = SharedClipboardWinOpen(pCtx->hWnd);
+ if (RT_SUCCESS(rc))
+ {
+ UINT uCurFormat = 0; /* Must be set to zero for EnumClipboardFormats(). */
+ while ((uCurFormat = EnumClipboardFormats(uCurFormat)) != 0)
+ fFormats |= SharedClipboardWinClipboardFormatToVBox(uCurFormat);
+
+ int rc2 = SharedClipboardWinClose();
+ AssertRC(rc2);
+ LogFlowFunc(("fFormats=%#x\n", fFormats));
+ }
+ else
+ LogFunc(("Failed with rc=%Rrc (fFormats=%#x)\n", rc, fFormats));
+
+ *pfFormats = fFormats;
+ return rc;
+}
+
+/**
+ * Extracts a field value from CF_HTML data.
+ *
+ * @returns VBox status code.
+ * @param pszSrc source in CF_HTML format.
+ * @param pszOption Name of CF_HTML field.
+ * @param puValue Where to return extracted value of CF_HTML field.
+ */
+int SharedClipboardWinGetCFHTMLHeaderValue(const char *pszSrc, const char *pszOption, uint32_t *puValue)
+{
+ AssertPtrReturn(pszSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszOption, VERR_INVALID_POINTER);
+
+ int rc = VERR_INVALID_PARAMETER;
+
+ const char *pszOptionValue = RTStrStr(pszSrc, pszOption);
+ if (pszOptionValue)
+ {
+ size_t cchOption = strlen(pszOption);
+ Assert(cchOption);
+
+ rc = RTStrToUInt32Ex(pszOptionValue + cchOption, NULL, 10, puValue);
+ }
+ return rc;
+}
+
+/**
+ * Check that the source string contains CF_HTML struct.
+ *
+ * @returns @c true if the @a pszSource string is in CF_HTML format.
+ * @param pszSource Source string to check.
+ */
+bool SharedClipboardWinIsCFHTML(const char *pszSource)
+{
+ return RTStrStr(pszSource, "Version:") != NULL
+ && RTStrStr(pszSource, "StartHTML:") != NULL;
+}
+
+/**
+ * Converts clipboard data from CF_HTML format to MIME clipboard format.
+ *
+ * Returns allocated buffer that contains html converted to text/html mime type
+ *
+ * @returns VBox status code.
+ * @param pszSource The input.
+ * @param cch The length of the input.
+ * @param ppszOutput Where to return the result. Free using RTMemFree.
+ * @param pcbOutput Where to the return length of the result (bytes/chars).
+ */
+int SharedClipboardWinConvertCFHTMLToMIME(const char *pszSource, const uint32_t cch, char **ppszOutput, uint32_t *pcbOutput)
+{
+ Assert(pszSource);
+ Assert(cch);
+ Assert(ppszOutput);
+ Assert(pcbOutput);
+
+ uint32_t offStart;
+ int rc = SharedClipboardWinGetCFHTMLHeaderValue(pszSource, "StartFragment:", &offStart);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t offEnd;
+ rc = SharedClipboardWinGetCFHTMLHeaderValue(pszSource, "EndFragment:", &offEnd);
+ if (RT_SUCCESS(rc))
+ {
+ if ( offStart > 0
+ && offEnd > 0
+ && offEnd >= offStart
+ && offEnd <= cch)
+ {
+ uint32_t cchSubStr = offEnd - offStart;
+ char *pszResult = (char *)RTMemAlloc(cchSubStr + 1);
+ if (pszResult)
+ {
+ rc = RTStrCopyEx(pszResult, cchSubStr + 1, pszSource + offStart, cchSubStr);
+ if (RT_SUCCESS(rc))
+ {
+ *ppszOutput = pszResult;
+ *pcbOutput = (uint32_t)(cchSubStr + 1);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected EndFragment. rc = %Rrc\n", rc));
+ RTMemFree(pszResult);
+ }
+ }
+ else
+ {
+ LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected EndFragment\n"));
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ {
+ LogRelFlowFunc(("Error: CF_HTML out of bounds - offStart=%#x offEnd=%#x cch=%#x\n", offStart, offEnd, cch));
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected EndFragment. rc = %Rrc\n", rc));
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected StartFragment. rc = %Rrc\n", rc));
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+/**
+ * Converts source UTF-8 MIME HTML clipboard data to UTF-8 CF_HTML format.
+ *
+ * This is just encapsulation work, slapping a header on the data.
+ *
+ * It allocates [..]
+ *
+ * Calculations:
+ * Header length = format Length + (2*(10 - 5('%010d'))('digits')) - 2('%s') = format length + 8
+ * EndHtml = Header length + fragment length
+ * StartHtml = 105(constant)
+ * StartFragment = 141(constant) may vary if the header html content will be extended
+ * EndFragment = Header length + fragment length - 38(ending length)
+ *
+ * For more format details, check out:
+ * https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767917(v=vs.85)
+ *
+ * @returns VBox status code.
+ * @param pszSource Source buffer that contains utf-16 string in mime html format
+ * @param cb Size of source buffer in bytes
+ * @param ppszOutput Where to return the allocated output buffer to put converted UTF-8
+ * CF_HTML clipboard data. This function allocates memory for this.
+ * @param pcbOutput Where to return the size of allocated result buffer in bytes/chars, including zero terminator
+ *
+ * @note output buffer should be free using RTMemFree()
+ * @note Everything inside of fragment can be UTF8. Windows allows it. Everything in header should be Latin1.
+ */
+int SharedClipboardWinConvertMIMEToCFHTML(const char *pszSource, size_t cb, char **ppszOutput, uint32_t *pcbOutput)
+{
+ Assert(ppszOutput);
+ Assert(pcbOutput);
+ Assert(pszSource);
+ Assert(cb);
+
+ /*
+ * Check that input UTF-8 and properly zero terminated.
+ * Note! The zero termination may come earlier than 'cb' - 1, that's fine.
+ */
+ int rc = RTStrValidateEncodingEx(pszSource, cb, RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else
+ {
+ LogRelFlowFunc(("Error: invalid source fragment. rc = %Rrc\n", rc));
+ return rc;
+ }
+ size_t const cchFragment = strlen(pszSource); /* Unfortunately the validator doesn't return the length. */
+
+ /*
+ * @StartHtml - Absolute offset of <html>
+ * @EndHtml - Size of the whole resulting text (excluding ending zero char)
+ * @StartFragment - Absolute position after <!--StartFragment-->
+ * @EndFragment - Absolute position of <!--EndFragment-->
+ *
+ * Note! The offset are zero padded to max width so we don't have any variations due to those.
+ * Note! All values includes CRLFs inserted into text.
+ *
+ * Calculations:
+ * Header length = Format sample length - 2 ('%s')
+ * EndHtml = Header length + fragment length
+ * StartHtml = 101(constant)
+ * StartFragment = 137(constant)
+ * EndFragment = Header length + fragment length - 38 (ending length)
+ */
+ static const char s_szFormatSample[] =
+ /* 0: */ "Version:1.0\r\n"
+ /* 13: */ "StartHTML:000000101\r\n"
+ /* 34: */ "EndHTML:%0000009u\r\n" // END HTML = Header length + fragment length
+ /* 53: */ "StartFragment:000000137\r\n"
+ /* 78: */ "EndFragment:%0000009u\r\n"
+ /* 101: */ "<html>\r\n"
+ /* 109: */ "<body>\r\n"
+ /* 117: */ "<!--StartFragment-->"
+ /* 137: */ "%s"
+ /* 137+2: */ "<!--EndFragment-->\r\n"
+ /* 157+2: */ "</body>\r\n"
+ /* 166+2: */ "</html>\r\n"
+ /* 175+2: */ ;
+ AssertCompile(sizeof(s_szFormatSample) == 175 + 2 + 1);
+
+ /* Calculate parameters of the CF_HTML header */
+ size_t const cchHeader = sizeof(s_szFormatSample) - 2 /*%s*/ - 1 /*'\0'*/;
+ size_t const offEndHtml = cchHeader + cchFragment;
+ size_t const offEndFragment = cchHeader + cchFragment - 38; /* 175-137 = 38 */
+ char *pszResult = (char *)RTMemAlloc(offEndHtml + 1);
+ AssertLogRelReturn(pszResult, VERR_NO_MEMORY);
+
+ /* Format resulting CF_HTML string: */
+ size_t cchFormatted = RTStrPrintf(pszResult, offEndHtml + 1, s_szFormatSample, offEndHtml, offEndFragment, pszSource);
+ Assert(offEndHtml == cchFormatted);
+
+#ifdef VBOX_STRICT
+ /*
+ * Check the calculations.
+ */
+
+ /* check 'StartFragment:' value */
+ static const char s_szStartFragment[] = "<!--StartFragment-->";
+ const char *pszRealStartFragment = RTStrStr(pszResult, s_szStartFragment);
+ Assert(&pszRealStartFragment[sizeof(s_szStartFragment) - 1] - pszResult == 137);
+
+ /* check 'EndFragment:' value */
+ static const char s_szEndFragment[] = "<!--EndFragment-->";
+ const char *pszRealEndFragment = RTStrStr(pszResult, s_szEndFragment);
+ Assert((size_t)(pszRealEndFragment - pszResult) == offEndFragment);
+#endif
+
+ *ppszOutput = pszResult;
+ *pcbOutput = (uint32_t)cchFormatted + 1;
+ Assert(*pcbOutput == cchFormatted + 1);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Handles the WM_CHANGECBCHAIN code.
+ *
+ * @returns LRESULT
+ * @param pWinCtx Windows context to use.
+ * @param hWnd Window handle to use.
+ * @param msg Message ID to pass on.
+ * @param wParam wParam to pass on
+ * @param lParam lParam to pass on.
+ */
+LRESULT SharedClipboardWinHandleWMChangeCBChain(PSHCLWINCTX pWinCtx,
+ HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ LRESULT lresultRc = 0;
+
+ LogFlowFuncEnter();
+
+ if (SharedClipboardWinIsNewAPI(&pWinCtx->newAPI))
+ {
+ lresultRc = DefWindowProc(hWnd, msg, wParam, lParam);
+ }
+ else /* Old API */
+ {
+ HWND hwndRemoved = (HWND)wParam;
+ HWND hwndNext = (HWND)lParam;
+
+ if (hwndRemoved == pWinCtx->hWndNextInChain)
+ {
+ /* The window that was next to our in the chain is being removed.
+ * Relink to the new next window.
+ */
+ pWinCtx->hWndNextInChain = hwndNext;
+ }
+ else
+ {
+ if (pWinCtx->hWndNextInChain)
+ {
+ /* Pass the message further. */
+ DWORD_PTR dwResult;
+ lresultRc = SendMessageTimeout(pWinCtx->hWndNextInChain, WM_CHANGECBCHAIN, wParam, lParam, 0,
+ SHCL_WIN_CBCHAIN_TIMEOUT_MS,
+ &dwResult);
+ if (!lresultRc)
+ lresultRc = (LRESULT)dwResult;
+ }
+ }
+ }
+
+ LogFlowFunc(("lresultRc=%ld\n", lresultRc));
+ return lresultRc;
+}
+
+/**
+ * Handles the WM_DESTROY code.
+ *
+ * @returns VBox status code.
+ * @param pWinCtx Windows context to use.
+ */
+int SharedClipboardWinHandleWMDestroy(PSHCLWINCTX pWinCtx)
+{
+ LogFlowFuncEnter();
+
+ int rc = VINF_SUCCESS;
+
+ /* MS recommends to remove from Clipboard chain in this callback. */
+ SharedClipboardWinChainRemove(pWinCtx);
+
+ if (pWinCtx->oldAPI.timerRefresh)
+ {
+ Assert(pWinCtx->hWnd);
+ KillTimer(pWinCtx->hWnd, 0);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Handles the WM_RENDERALLFORMATS message.
+ *
+ * @returns VBox status code.
+ * @param pWinCtx Windows context to use.
+ * @param hWnd Window handle to use.
+ */
+int SharedClipboardWinHandleWMRenderAllFormats(PSHCLWINCTX pWinCtx, HWND hWnd)
+{
+ RT_NOREF(pWinCtx);
+
+ LogFlowFuncEnter();
+
+ /* Do nothing. The clipboard formats will be unavailable now, because the
+ * windows is to be destroyed and therefore the guest side becomes inactive.
+ */
+ int rc = SharedClipboardWinOpen(hWnd);
+ if (RT_SUCCESS(rc))
+ {
+ SharedClipboardWinClear();
+ SharedClipboardWinClose();
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Handles the WM_TIMER code, which is needed if we're running with the so-called "old" Windows clipboard API.
+ * Does nothing if we're running with the "new" Windows API.
+ *
+ * @returns VBox status code.
+ * @param pWinCtx Windows context to use.
+ */
+int SharedClipboardWinHandleWMTimer(PSHCLWINCTX pWinCtx)
+{
+ int rc = VINF_SUCCESS;
+
+ if (!SharedClipboardWinIsNewAPI(&pWinCtx->newAPI)) /* Only run when using the "old" Windows API. */
+ {
+ LogFlowFuncEnter();
+
+ HWND hViewer = GetClipboardViewer();
+
+ /* Re-register ourselves in the clipboard chain if our last ping
+ * timed out or there seems to be no valid chain. */
+ if (!hViewer || pWinCtx->oldAPI.fCBChainPingInProcess)
+ {
+ SharedClipboardWinChainRemove(pWinCtx);
+ SharedClipboardWinChainAdd(pWinCtx);
+ }
+
+ /* Start a new ping by passing a dummy WM_CHANGECBCHAIN to be
+ * processed by ourselves to the chain. */
+ pWinCtx->oldAPI.fCBChainPingInProcess = TRUE;
+
+ hViewer = GetClipboardViewer();
+ if (hViewer)
+ SendMessageCallback(hViewer, WM_CHANGECBCHAIN, (WPARAM)pWinCtx->hWndNextInChain, (LPARAM)pWinCtx->hWndNextInChain,
+ SharedClipboardWinChainPingProc, (ULONG_PTR)pWinCtx);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Announces a clipboard format to the Windows clipboard.
+ *
+ * The actual rendering (setting) of the clipboard data will be done later with
+ * a separate WM_RENDERFORMAT message.
+ *
+ * @returns VBox status code. VERR_NOT_SUPPORTED if the format is not supported / handled.
+ * @param pWinCtx Windows context to use.
+ * @param fFormats Clipboard format(s) to announce.
+ */
+static int sharedClipboardWinAnnounceFormats(PSHCLWINCTX pWinCtx, SHCLFORMATS fFormats)
+{
+ LogFunc(("fFormats=0x%x\n", fFormats));
+
+ /*
+ * Set the clipboard formats.
+ */
+ static struct
+ {
+ uint32_t fVBoxFormat;
+ UINT uWinFormat;
+ const char *pszWinFormat;
+ const char *pszLog;
+ } s_aFormats[] =
+ {
+ { VBOX_SHCL_FMT_UNICODETEXT, CF_UNICODETEXT, NULL, "CF_UNICODETEXT" },
+ { VBOX_SHCL_FMT_BITMAP, CF_DIB, NULL, "CF_DIB" },
+ { VBOX_SHCL_FMT_HTML, 0, SHCL_WIN_REGFMT_HTML, "SHCL_WIN_REGFMT_HTML" },
+ };
+ unsigned cSuccessfullySet = 0;
+ SHCLFORMATS fFormatsLeft = fFormats;
+ int rc = VINF_SUCCESS;
+ for (uintptr_t i = 0; i < RT_ELEMENTS(s_aFormats) && fFormatsLeft != 0; i++)
+ {
+ if (fFormatsLeft & s_aFormats[i].fVBoxFormat)
+ {
+ LogFunc(("%s\n", s_aFormats[i].pszLog));
+ fFormatsLeft &= ~s_aFormats[i].fVBoxFormat;
+
+ /* Reg format if needed: */
+ UINT uWinFormat = s_aFormats[i].uWinFormat;
+ if (!uWinFormat)
+ {
+ uWinFormat = RegisterClipboardFormat(s_aFormats[i].pszWinFormat);
+ AssertContinue(uWinFormat != 0);
+ }
+
+ /* Tell the clipboard we've got data upon a request. We check the
+ last error here as hClip will be NULL even on success (despite
+ what MSDN says). */
+ SetLastError(NO_ERROR);
+ HANDLE hClip = SetClipboardData(uWinFormat, NULL);
+ DWORD dwErr = GetLastError();
+ if (dwErr == NO_ERROR || hClip != NULL)
+ cSuccessfullySet++;
+ else
+ {
+ AssertMsgFailed(("%s/%u: %u\n", s_aFormats[i].pszLog, uWinFormat, dwErr));
+ rc = RTErrConvertFromWin32(dwErr);
+ }
+ }
+ }
+
+ /*
+ * Consider setting anything a success, converting any error into
+ * informational status. Unsupport error only happens if all formats
+ * were unsupported.
+ */
+ if (cSuccessfullySet > 0)
+ {
+ pWinCtx->hWndClipboardOwnerUs = GetClipboardOwner();
+ if (RT_FAILURE(rc))
+ rc = -rc;
+ }
+ else if (RT_SUCCESS(rc) && fFormatsLeft != 0)
+ {
+ LogFunc(("Unsupported formats: %#x (%#x)\n", fFormatsLeft, fFormats));
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Opens the clipboard, clears it, announces @a fFormats and closes it.
+ *
+ * The actual rendering (setting) of the clipboard data will be done later with
+ * a separate WM_RENDERFORMAT message.
+ *
+ * @returns VBox status code. VERR_NOT_SUPPORTED if the format is not supported / handled.
+ * @param pWinCtx Windows context to use.
+ * @param fFormats Clipboard format(s) to announce.
+ * @param hWnd The window handle to use as owner.
+ */
+int SharedClipboardWinClearAndAnnounceFormats(PSHCLWINCTX pWinCtx, SHCLFORMATS fFormats, HWND hWnd)
+{
+ int rc = SharedClipboardWinOpen(hWnd);
+ if (RT_SUCCESS(rc))
+ {
+ SharedClipboardWinClear();
+
+ rc = sharedClipboardWinAnnounceFormats(pWinCtx, fFormats);
+ Assert(pWinCtx->hWndClipboardOwnerUs == hWnd || pWinCtx->hWndClipboardOwnerUs == NULL);
+
+ SharedClipboardWinClose();
+ }
+ return rc;
+}
+
+/**
+ * Writes (places) clipboard data into the Windows clipboard.
+ *
+ * @returns VBox status code.
+ * @param cfFormat Windows clipboard format to write data for.
+ * @param pvData Pointer to actual clipboard data to write.
+ * @param cbData Size (in bytes) of actual clipboard data to write.
+ *
+ * @note ASSUMES that the clipboard has already been opened.
+ */
+int SharedClipboardWinDataWrite(UINT cfFormat, void *pvData, uint32_t cbData)
+{
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn (cbData, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+
+ HANDLE hMem = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, cbData);
+
+ LogFlowFunc(("hMem=%p\n", hMem));
+
+ if (hMem)
+ {
+ void *pMem = GlobalLock(hMem);
+
+ LogFlowFunc(("pMem=%p, GlobalSize=%zu\n", pMem, GlobalSize(hMem)));
+
+ if (pMem)
+ {
+ LogFlowFunc(("Setting data\n"));
+
+ memcpy(pMem, pvData, cbData);
+
+ /* The memory must be unlocked before inserting to the Clipboard. */
+ GlobalUnlock(hMem);
+
+ /* 'hMem' contains the host clipboard data.
+ * size is 'cb' and format is 'format'.
+ */
+ HANDLE hClip = SetClipboardData(cfFormat, hMem);
+
+ LogFlowFunc(("hClip=%p\n", hClip));
+
+ if (hClip)
+ {
+ /* The hMem ownership has gone to the system. Nothing to do. */
+ }
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+ }
+ else
+ rc = VERR_ACCESS_DENIED;
+
+ GlobalFree(hMem);
+ }
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+
+ if (RT_FAILURE(rc))
+ LogFunc(("Setting clipboard data failed with %Rrc\n", rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+
+/**
+ * Creates an Shared Clipboard transfer by announcing transfer data (via IDataObject) to Windows.
+ *
+ * This creates the necessary IDataObject + IStream implementations and initiates the actual transfers required for getting
+ * the meta data. Whether or not the actual (file++) transfer(s) are happening is up to the user (at some point) later then.
+ *
+ * @returns VBox status code.
+ * @param pWinCtx Windows context to use.
+ * @param pTransferCtxCtx Transfer contextto use.
+ * @param pTransfer Shared Clipboard transfer to use.
+ */
+int SharedClipboardWinTransferCreate(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pWinCtx=%p\n", pWinCtx));
+
+ AssertReturn(pTransfer->pvUser == NULL, VERR_WRONG_ORDER);
+
+ /* Make sure to enter the critical section before setting the clipboard data, as otherwise WM_CLIPBOARDUPDATE
+ * might get called *before* we had the opportunity to set pWinCtx->hWndClipboardOwnerUs below. */
+ int rc = RTCritSectEnter(&pWinCtx->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ SharedClipboardWinTransferCtx *pWinURITransferCtx = new SharedClipboardWinTransferCtx();
+ if (pWinURITransferCtx)
+ {
+ pTransfer->pvUser = pWinURITransferCtx;
+ pTransfer->cbUser = sizeof(SharedClipboardWinTransferCtx);
+
+ pWinURITransferCtx->pDataObj = new SharedClipboardWinDataObject(pTransfer);
+ if (pWinURITransferCtx->pDataObj)
+ {
+ rc = pWinURITransferCtx->pDataObj->Init();
+ if (RT_SUCCESS(rc))
+ {
+ SharedClipboardWinClose();
+ /* Note: Clipboard must be closed first before calling OleSetClipboard(). */
+
+ /** @todo There is a potential race between SharedClipboardWinClose() and OleSetClipboard(),
+ * where another application could own the clipboard (open), and thus the call to
+ * OleSetClipboard() will fail. Needs (better) fixing. */
+ HRESULT hr = S_OK;
+
+ for (unsigned uTries = 0; uTries < 3; uTries++)
+ {
+ hr = OleSetClipboard(pWinURITransferCtx->pDataObj);
+ if (SUCCEEDED(hr))
+ {
+ Assert(OleIsCurrentClipboard(pWinURITransferCtx->pDataObj) == S_OK); /* Sanity. */
+
+ /*
+ * Calling OleSetClipboard() changed the clipboard owner, which in turn will let us receive
+ * a WM_CLIPBOARDUPDATE message. To not confuse ourselves with our own clipboard owner changes,
+ * save a new window handle and deal with it in WM_CLIPBOARDUPDATE.
+ */
+ pWinCtx->hWndClipboardOwnerUs = GetClipboardOwner();
+
+ LogFlowFunc(("hWndClipboardOwnerUs=%p\n", pWinCtx->hWndClipboardOwnerUs));
+ break;
+ }
+
+ LogFlowFunc(("Failed with %Rhrc (try %u/3)\n", hr, uTries + 1));
+ RTThreadSleep(500); /* Wait a bit. */
+ }
+
+ if (FAILED(hr))
+ {
+ rc = VERR_ACCESS_DENIED; /** @todo Fudge; fix this. */
+ LogRel(("Shared Clipboard: Failed with %Rhrc when setting data object to clipboard\n", hr));
+ }
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ int rc2 = RTCritSectLeave(&pWinCtx->CritSect);
+ AssertRC(rc2);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Destroys implementation-specific data for an Shared Clipboard transfer.
+ *
+ * @param pWinCtx Windows context to use.
+ * @param pTransfer Shared Clipboard transfer to create implementation-specific data for.
+ */
+void SharedClipboardWinTransferDestroy(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer)
+{
+ RT_NOREF(pWinCtx);
+
+ if (!pTransfer)
+ return;
+
+ LogFlowFuncEnter();
+
+ if (pTransfer->pvUser)
+ {
+ Assert(pTransfer->cbUser == sizeof(SharedClipboardWinTransferCtx));
+ SharedClipboardWinTransferCtx *pWinURITransferCtx = (SharedClipboardWinTransferCtx *)pTransfer->pvUser;
+ Assert(pWinURITransferCtx);
+
+ if (pWinURITransferCtx->pDataObj)
+ {
+ delete pWinURITransferCtx->pDataObj;
+ pWinURITransferCtx->pDataObj = NULL;
+ }
+
+ delete pWinURITransferCtx;
+
+ pTransfer->pvUser = NULL;
+ pTransfer->cbUser = 0;
+ }
+}
+
+/**
+ * Retrieves the roots for a transfer by opening the clipboard and getting the clipboard data
+ * as string list (CF_HDROP), assigning it to the transfer as roots then.
+ *
+ * @returns VBox status code.
+ * @param pWinCtx Windows context to use.
+ * @param pTransfer Transfer to get roots for.
+ */
+int SharedClipboardWinGetRoots(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pWinCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ Assert(ShClTransferGetSource(pTransfer) == SHCLSOURCE_LOCAL); /* Sanity. */
+
+ int rc = SharedClipboardWinOpen(pWinCtx->hWnd);
+ if (RT_SUCCESS(rc))
+ {
+ /* The data data in CF_HDROP format, as the files are locally present and don't need to be
+ * presented as a IDataObject or IStream. */
+ HANDLE hClip = hClip = GetClipboardData(CF_HDROP);
+ if (hClip)
+ {
+ HDROP hDrop = (HDROP)GlobalLock(hClip);
+ if (hDrop)
+ {
+ char *papszList = NULL;
+ uint32_t cbList;
+ rc = SharedClipboardWinDropFilesToStringList((DROPFILES *)hDrop, &papszList, &cbList);
+
+ GlobalUnlock(hClip);
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = ShClTransferRootsSet(pTransfer,
+ papszList, cbList + 1 /* Include termination */);
+ RTStrFree(papszList);
+ }
+ }
+ else
+ LogRel(("Shared Clipboard: Unable to lock clipboard data, last error: %ld\n", GetLastError()));
+ }
+ else
+ LogRel(("Shared Clipboard: Unable to retrieve clipboard data from clipboard (CF_HDROP), last error: %ld\n",
+ GetLastError()));
+
+ SharedClipboardWinClose();
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Converts a DROPFILES (HDROP) structure to a string list, separated by \r\n.
+ * Does not do any locking on the input data.
+ *
+ * @returns VBox status code.
+ * @param pDropFiles Pointer to DROPFILES structure to convert.
+ * @param papszList Where to store the allocated string list.
+ * @param pcbList Where to store the size (in bytes) of the allocated string list.
+ */
+int SharedClipboardWinDropFilesToStringList(DROPFILES *pDropFiles, char **papszList, uint32_t *pcbList)
+{
+ AssertPtrReturn(pDropFiles, VERR_INVALID_POINTER);
+ AssertPtrReturn(papszList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbList, VERR_INVALID_POINTER);
+
+ /* Do we need to do Unicode stuff? */
+ const bool fUnicode = RT_BOOL(pDropFiles->fWide);
+
+ /* Get the offset of the file list. */
+ Assert(pDropFiles->pFiles >= sizeof(DROPFILES));
+
+ /* Note: This is *not* pDropFiles->pFiles! DragQueryFile only
+ * will work with the plain storage medium pointer! */
+ HDROP hDrop = (HDROP)(pDropFiles);
+
+ int rc = VINF_SUCCESS;
+
+ /* First, get the file count. */
+ /** @todo Does this work on Windows 2000 / NT4? */
+ char *pszFiles = NULL;
+ uint32_t cchFiles = 0;
+ UINT cFiles = DragQueryFile(hDrop, UINT32_MAX /* iFile */, NULL /* lpszFile */, 0 /* cchFile */);
+
+ LogFlowFunc(("Got %RU16 file(s), fUnicode=%RTbool\n", cFiles, fUnicode));
+
+ for (UINT i = 0; i < cFiles; i++)
+ {
+ UINT cchFile = DragQueryFile(hDrop, i /* File index */, NULL /* Query size first */, 0 /* cchFile */);
+ Assert(cchFile);
+
+ if (RT_FAILURE(rc))
+ break;
+
+ char *pszFileUtf8 = NULL; /* UTF-8 version. */
+ UINT cchFileUtf8 = 0;
+ if (fUnicode)
+ {
+ /* Allocate enough space (including terminator). */
+ WCHAR *pwszFile = (WCHAR *)RTMemAlloc((cchFile + 1) * sizeof(WCHAR));
+ if (pwszFile)
+ {
+ const UINT cwcFileUtf16 = DragQueryFileW(hDrop, i /* File index */,
+ pwszFile, cchFile + 1 /* Include terminator */);
+
+ AssertMsg(cwcFileUtf16 == cchFile, ("cchFileUtf16 (%RU16) does not match cchFile (%RU16)\n",
+ cwcFileUtf16, cchFile));
+ RT_NOREF(cwcFileUtf16);
+
+ rc = RTUtf16ToUtf8(pwszFile, &pszFileUtf8);
+ if (RT_SUCCESS(rc))
+ {
+ cchFileUtf8 = (UINT)strlen(pszFileUtf8);
+ Assert(cchFileUtf8);
+ }
+
+ RTMemFree(pwszFile);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else /* ANSI */
+ {
+ /* Allocate enough space (including terminator). */
+ char *pszFileANSI = (char *)RTMemAlloc((cchFile + 1) * sizeof(char));
+ UINT cchFileANSI = 0;
+ if (pszFileANSI)
+ {
+ cchFileANSI = DragQueryFileA(hDrop, i /* File index */,
+ pszFileANSI, cchFile + 1 /* Include terminator */);
+
+ AssertMsg(cchFileANSI == cchFile, ("cchFileANSI (%RU16) does not match cchFile (%RU16)\n",
+ cchFileANSI, cchFile));
+
+ /* Convert the ANSI codepage to UTF-8. */
+ rc = RTStrCurrentCPToUtf8(&pszFileUtf8, pszFileANSI);
+ if (RT_SUCCESS(rc))
+ {
+ cchFileUtf8 = (UINT)strlen(pszFileUtf8);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("\tFile: %s (cchFile=%RU16)\n", pszFileUtf8, cchFileUtf8));
+
+ LogRel2(("Shared Clipboard: Adding file '%s' to transfer\n", pszFileUtf8));
+
+ rc = RTStrAAppendExN(&pszFiles, 1 /* cPairs */, pszFileUtf8, strlen(pszFileUtf8));
+ cchFiles += (uint32_t)strlen(pszFileUtf8);
+ }
+
+ if (pszFileUtf8)
+ RTStrFree(pszFileUtf8);
+
+ if (RT_FAILURE(rc))
+ {
+ LogFunc(("Error handling file entry #%u, rc=%Rrc\n", i, rc));
+ break;
+ }
+
+ /* Add separation between filenames.
+ * Note: Also do this for the last element of the list. */
+ rc = RTStrAAppendExN(&pszFiles, 1 /* cPairs */, "\r\n", 2 /* Bytes */);
+ if (RT_SUCCESS(rc))
+ cchFiles += 2; /* Include \r\n */
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ cchFiles += 1; /* Add string termination. */
+ uint32_t cbFiles = cchFiles * sizeof(char); /* UTF-8. */
+
+ LogFlowFunc(("cFiles=%u, cchFiles=%RU32, cbFiles=%RU32, pszFiles=0x%p\n",
+ cFiles, cchFiles, cbFiles, pszFiles));
+
+ *papszList = pszFiles;
+ *pcbList = cbFiles;
+ }
+ else
+ {
+ if (pszFiles)
+ RTStrFree(pszFiles);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
+
diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp
new file mode 100644
index 00000000..8e1bc162
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp
@@ -0,0 +1,2428 @@
+/** @file
+ * Shared Clipboard: Common X11 code.
+ */
+
+/*
+ * Copyright (C) 2006-2022 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
+ */
+
+/* Note: to automatically run regression tests on the Shared Clipboard,
+ * execute the tstClipboardGH-X11 testcase. If you often make changes to the
+ * clipboard code, adding the line
+ *
+ * OTHERS += $(PATH_tstClipboardGH-X11)/tstClipboardGH-X11.run
+ *
+ * to LocalConfig.kmk will cause the tests to be run every time the code is
+ * changed.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+
+#include <errno.h>
+
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifdef RT_OS_SOLARIS
+#include <tsol/label.h>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Intrinsic.h>
+#include <X11/Shell.h>
+#include <X11/Xproto.h>
+#include <X11/StringDefs.h>
+
+#include <iprt/assert.h>
+#include <iprt/types.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/utf16.h>
+#include <iprt/uri.h>
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+# include <iprt/cpp/list.h>
+# include <iprt/cpp/ministring.h>
+# include <VBox/GuestHost/SharedClipboard-transfers.h>
+#endif
+
+#include <VBox/log.h>
+#include <VBox/version.h>
+
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/GuestHost/SharedClipboard-x11.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+
+/** Own macro for declaring function visibility / linkage based on whether this
+ * code runs as part of test cases or not. */
+#ifdef TESTCASE
+# define SHCL_X11_DECL(x) x
+#else
+# define SHCL_X11_DECL(x) static x
+#endif
+
+
+/*********************************************************************************************************************************
+* Externals *
+*********************************************************************************************************************************/
+#ifdef TESTCASE
+extern void tstThreadScheduleCall(void (*proc)(void *, void *), void *client_data);
+extern void tstClipRequestData(SHCLX11CTX* pCtx, SHCLX11FMTIDX target, void *closure);
+extern void tstRequestTargets(SHCLX11CTX* pCtx);
+#endif
+
+
+/*********************************************************************************************************************************
+* Prototypes *
+*********************************************************************************************************************************/
+class formats;
+SHCL_X11_DECL(Atom) clipGetAtom(PSHCLX11CTX pCtx, const char *pszName);
+SHCL_X11_DECL(void) clipQueryX11Targets(PSHCLX11CTX pCtx);
+
+static int clipInitInternal(PSHCLX11CTX pCtx);
+static void clipUninitInternal(PSHCLX11CTX pCtx);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/**
+ * The table maps X11 names to data formats
+ * and to the corresponding VBox clipboard formats.
+ */
+SHCL_X11_DECL(SHCLX11FMTTABLE) g_aFormats[] =
+{
+ { "INVALID", SHCLX11FMT_INVALID, VBOX_SHCL_FMT_NONE },
+
+ { "UTF8_STRING", SHCLX11FMT_UTF8, VBOX_SHCL_FMT_UNICODETEXT },
+ { "text/plain;charset=UTF-8", SHCLX11FMT_UTF8, VBOX_SHCL_FMT_UNICODETEXT },
+ { "text/plain;charset=utf-8", SHCLX11FMT_UTF8, VBOX_SHCL_FMT_UNICODETEXT },
+ { "STRING", SHCLX11FMT_TEXT, VBOX_SHCL_FMT_UNICODETEXT },
+ { "TEXT", SHCLX11FMT_TEXT, VBOX_SHCL_FMT_UNICODETEXT },
+ { "text/plain", SHCLX11FMT_TEXT, VBOX_SHCL_FMT_UNICODETEXT },
+
+ { "text/html", SHCLX11FMT_HTML, VBOX_SHCL_FMT_HTML },
+ { "text/html;charset=utf-8", SHCLX11FMT_HTML, VBOX_SHCL_FMT_HTML },
+ { "application/x-moz-nativehtml", SHCLX11FMT_HTML, VBOX_SHCL_FMT_HTML },
+
+ { "image/bmp", SHCLX11FMT_BMP, VBOX_SHCL_FMT_BITMAP },
+ { "image/x-bmp", SHCLX11FMT_BMP, VBOX_SHCL_FMT_BITMAP },
+ { "image/x-MS-bmp", SHCLX11FMT_BMP, VBOX_SHCL_FMT_BITMAP },
+ /** @todo Inkscape exports image/png but not bmp... */
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ { "text/uri-list", SHCLX11FMT_URI_LIST, VBOX_SHCL_FMT_URI_LIST },
+ { "x-special/gnome-copied-files", SHCLX11FMT_URI_LIST, VBOX_SHCL_FMT_URI_LIST },
+ { "x-special/nautilus-clipboard", SHCLX11FMT_URI_LIST, VBOX_SHCL_FMT_URI_LIST },
+ { "application/x-kde-cutselection", SHCLX11FMT_URI_LIST, VBOX_SHCL_FMT_URI_LIST },
+ /** @todo Anything else we need to add here? */
+ /** @todo Add Wayland / Weston support. */
+#endif
+};
+
+
+#ifdef TESTCASE
+# ifdef RT_OS_SOLARIS_10
+char XtStrings [] = "";
+WidgetClassRec* applicationShellWidgetClass;
+char XtShellStrings [] = "";
+int XmbTextPropertyToTextList(
+ Display* /* display */,
+ XTextProperty* /* text_prop */,
+ char*** /* list_return */,
+ int* /* count_return */
+)
+{
+ return 0;
+}
+# else
+const char XtStrings [] = "";
+_WidgetClassRec* applicationShellWidgetClass;
+const char XtShellStrings [] = "";
+# endif /* RT_OS_SOLARIS_10 */
+#endif /* TESTCASE */
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+
+#define SHCL_MAX_X11_FORMATS RT_ELEMENTS(g_aFormats)
+
+
+/*********************************************************************************************************************************
+* Internal structures *
+*********************************************************************************************************************************/
+
+/**
+ * A structure containing information about where to store a request
+ * for the X11 clipboard contents.
+ */
+typedef struct _CLIPREADX11CBREQ
+{
+ /** The format VBox would like the data in. */
+ SHCLFORMAT uFmtVBox;
+ /** The format we requested from X11. */
+ SHCLX11FMTIDX idxFmtX11;
+ /** The clipboard context this request is associated with. */
+ SHCLX11CTX *pCtx;
+ /** The request structure passed in from the backend. */
+ CLIPREADCBREQ *pReq;
+} CLIPREADX11CBREQ;
+
+
+
+#ifdef TESTCASE
+/**
+ * Return the max. number of elements in the X11 format table.
+ * Used by the testing code in tstClipboardGH-X11.cpp
+ * which cannot use RT_ELEMENTS(g_aFormats) directly.
+ *
+ * @return size_t The number of elements in the g_aFormats array.
+ */
+SHCL_X11_DECL(size_t) clipReportMaxX11Formats(void)
+{
+ return (RT_ELEMENTS(g_aFormats));
+}
+#endif
+
+/**
+ * Returns the atom corresponding to a supported X11 format.
+ *
+ * @returns Found atom to the corresponding X11 format.
+ * @param pCtx The X11 clipboard context to use.
+ * @param uFmtIdx Format index to look up atom for.
+ */
+static Atom clipAtomForX11Format(PSHCLX11CTX pCtx, SHCLX11FMTIDX uFmtIdx)
+{
+ AssertReturn(uFmtIdx < RT_ELEMENTS(g_aFormats), 0);
+ return clipGetAtom(pCtx, g_aFormats[uFmtIdx].pcszAtom);
+}
+
+/**
+ * Returns the SHCLX11FMT corresponding to a supported X11 format.
+ *
+ * @return SHCLX11FMT for a specific format index.
+ * @param uFmtIdx Format index to look up SHCLX11CLIPFMT for.
+ */
+SHCL_X11_DECL(SHCLX11FMT) clipRealFormatForX11Format(SHCLX11FMTIDX uFmtIdx)
+{
+ AssertReturn(uFmtIdx < RT_ELEMENTS(g_aFormats), SHCLX11FMT_INVALID);
+ return g_aFormats[uFmtIdx].enmFmtX11;
+}
+
+/**
+ * Returns the VBox format corresponding to a supported X11 format.
+ *
+ * @return SHCLFORMAT for a specific format index.
+ * @param uFmtIdx Format index to look up VBox format for.
+ */
+static SHCLFORMAT clipVBoxFormatForX11Format(SHCLX11FMTIDX uFmtIdx)
+{
+ AssertReturn(uFmtIdx < RT_ELEMENTS(g_aFormats), VBOX_SHCL_FMT_NONE);
+ return g_aFormats[uFmtIdx].uFmtVBox;
+}
+
+/**
+ * Looks up the X11 format matching a given X11 atom.
+ *
+ * @returns The format on success, NIL_CLIPX11FORMAT on failure.
+ * @param pCtx The X11 clipboard context to use.
+ * @param atomFormat Atom to look up X11 format for.
+ */
+static SHCLX11FMTIDX clipFindX11FormatByAtom(PSHCLX11CTX pCtx, Atom atomFormat)
+{
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
+ if (clipAtomForX11Format(pCtx, i) == atomFormat)
+ {
+ LogFlowFunc(("Returning index %u for atom '%s'\n", i, g_aFormats[i].pcszAtom));
+ return i;
+ }
+ return NIL_CLIPX11FORMAT;
+}
+
+/**
+ * Enumerates supported X11 clipboard formats corresponding to given VBox formats.
+ *
+ * @returns The next matching X11 format index in the list, or NIL_CLIPX11FORMAT if there are no more.
+ * @param uFormatsVBox VBox formats to enumerate supported X11 clipboard formats for.
+ * @param lastFmtIdx The value returned from the last call of this function.
+ * Use NIL_CLIPX11FORMAT to start the enumeration.
+ */
+static SHCLX11FMTIDX clipEnumX11Formats(SHCLFORMATS uFormatsVBox,
+ SHCLX11FMTIDX lastFmtIdx)
+{
+ for (unsigned i = lastFmtIdx + 1; i < RT_ELEMENTS(g_aFormats); ++i)
+ {
+ if (uFormatsVBox & clipVBoxFormatForX11Format(i))
+ return i;
+ }
+
+ return NIL_CLIPX11FORMAT;
+}
+
+/**
+ * Array of structures for mapping Xt widgets to context pointers. We
+ * need this because the widget clipboard callbacks do not pass user data.
+ */
+static struct
+{
+ /** Pointer to widget we want to associate the context with. */
+ Widget pWidget;
+ /** Pointer to X11 context associated with the widget. */
+ PSHCLX11CTX pCtx;
+} g_aContexts[VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX];
+
+/**
+ * Registers a new X11 clipboard context.
+ *
+ * @returns VBox status code.
+ * @param pCtx The X11 clipboard context to use.
+ */
+static int clipRegisterContext(PSHCLX11CTX pCtx)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_PARAMETER);
+
+ bool fFound = false;
+
+ Widget pWidget = pCtx->pWidget;
+ AssertReturn(pWidget != NULL, VERR_INVALID_PARAMETER);
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aContexts); ++i)
+ {
+ AssertReturn( (g_aContexts[i].pWidget != pWidget)
+ && (g_aContexts[i].pCtx != pCtx), VERR_WRONG_ORDER);
+ if (g_aContexts[i].pWidget == NULL && !fFound)
+ {
+ AssertReturn(g_aContexts[i].pCtx == NULL, VERR_INTERNAL_ERROR);
+ g_aContexts[i].pWidget = pWidget;
+ g_aContexts[i].pCtx = pCtx;
+ fFound = true;
+ }
+ }
+
+ return fFound ? VINF_SUCCESS : VERR_OUT_OF_RESOURCES;
+}
+
+/**
+ * Unregister an X11 clipboard context.
+ *
+ * @param pCtx The X11 clipboard context to use.
+ */
+static void clipUnregisterContext(PSHCLX11CTX pCtx)
+{
+ AssertPtrReturnVoid(pCtx);
+
+ Widget pWidget = pCtx->pWidget;
+ AssertPtrReturnVoid(pWidget);
+
+ bool fFound = false;
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aContexts); ++i)
+ {
+ Assert(!fFound || g_aContexts[i].pWidget != pWidget);
+ if (g_aContexts[i].pWidget == pWidget)
+ {
+ Assert(g_aContexts[i].pCtx != NULL);
+ g_aContexts[i].pWidget = NULL;
+ g_aContexts[i].pCtx = NULL;
+ fFound = true;
+ }
+ }
+}
+
+/**
+ * Finds a X11 clipboard context for a specific X11 widget.
+ *
+ * @returns Pointer to associated X11 clipboard context if found, or NULL if not found.
+ * @param pWidget X11 widget to return X11 clipboard context for.
+ */
+static PSHCLX11CTX clipLookupContext(Widget pWidget)
+{
+ AssertPtrReturn(pWidget, NULL);
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aContexts); ++i)
+ {
+ if (g_aContexts[i].pWidget == pWidget)
+ {
+ Assert(g_aContexts[i].pCtx != NULL);
+ return g_aContexts[i].pCtx;
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * Converts an atom name string to an X11 atom, looking it up in a cache before asking the server.
+ *
+ * @returns Found X11 atom.
+ * @param pCtx The X11 clipboard context to use.
+ * @param pcszName Name of atom to return atom for.
+ */
+SHCL_X11_DECL(Atom) clipGetAtom(PSHCLX11CTX pCtx, const char *pcszName)
+{
+ AssertPtrReturn(pcszName, None);
+ return XInternAtom(XtDisplay(pCtx->pWidget), pcszName, False);
+}
+
+/** String written to the wakeup pipe. */
+#define WAKE_UP_STRING "WakeUp!"
+/** Length of the string written. */
+#define WAKE_UP_STRING_LEN ( sizeof(WAKE_UP_STRING) - 1 )
+
+/**
+ * Schedules a function call to run on the Xt event thread by passing it to
+ * the application context as a 0ms timeout and waking up the event loop by
+ * writing to the wakeup pipe which it monitors.
+ */
+static int clipThreadScheduleCall(PSHCLX11CTX pCtx,
+ void (*proc)(void *, void *),
+ void *client_data)
+{
+ LogFlowFunc(("proc=%p, client_data=%p\n", proc, client_data));
+
+#ifndef TESTCASE
+ AssertReturn(pCtx, VERR_INVALID_POINTER);
+ AssertReturn(pCtx->pAppContext, VERR_INVALID_POINTER);
+
+ XtAppAddTimeOut(pCtx->pAppContext, 0, (XtTimerCallbackProc)proc,
+ (XtPointer)client_data);
+ ssize_t cbWritten = write(pCtx->wakeupPipeWrite, WAKE_UP_STRING, WAKE_UP_STRING_LEN);
+ Assert(cbWritten == WAKE_UP_STRING_LEN);
+ RT_NOREF(cbWritten);
+#else
+ RT_NOREF(pCtx);
+ tstThreadScheduleCall(proc, client_data);
+#endif
+
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Reports the formats currently supported by the X11 clipboard to VBox.
+ *
+ * @note Runs in Xt event thread.
+ *
+ * @param pCtx The X11 clipboard context to use.
+ */
+static void clipReportFormatsToVBox(PSHCLX11CTX pCtx)
+{
+ SHCLFORMATS vboxFmt = clipVBoxFormatForX11Format(pCtx->idxFmtText);
+ vboxFmt |= clipVBoxFormatForX11Format(pCtx->idxFmtBmp);
+ vboxFmt |= clipVBoxFormatForX11Format(pCtx->idxFmtHTML);
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ vboxFmt |= clipVBoxFormatForX11Format(pCtx->idxFmtURI);
+#endif
+
+ LogFlowFunc(("idxFmtText=%u ('%s'), idxFmtBmp=%u ('%s'), idxFmtHTML=%u ('%s')",
+ pCtx->idxFmtText, g_aFormats[pCtx->idxFmtText].pcszAtom,
+ pCtx->idxFmtBmp, g_aFormats[pCtx->idxFmtBmp].pcszAtom,
+ pCtx->idxFmtHTML, g_aFormats[pCtx->idxFmtHTML].pcszAtom));
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ LogFlowFunc((", idxFmtURI=%u ('%s')", pCtx->idxFmtURI, g_aFormats[pCtx->idxFmtURI].pcszAtom));
+#endif
+ LogFlow((" -> vboxFmt=%#x\n", vboxFmt));
+
+#ifdef LOG_ENABLED
+ char *pszFmts = ShClFormatsToStrA(vboxFmt);
+ AssertPtrReturnVoid(pszFmts);
+ LogRel2(("Shared Clipboard: X11 reported available VBox formats '%s'\n", pszFmts));
+ RTStrFree(pszFmts);
+#endif
+
+ pCtx->Callbacks.pfnReportFormats(pCtx->pFrontend, vboxFmt, NULL /* pvUser */);
+}
+
+/**
+ * Forgets which formats were previously in the X11 clipboard. Called when we
+ * grab the clipboard.
+ *
+ * @param pCtx The X11 clipboard context to use.
+ */
+static void clipResetX11Formats(PSHCLX11CTX pCtx)
+{
+ LogFlowFuncEnter();
+
+ pCtx->idxFmtText = 0;
+ pCtx->idxFmtBmp = 0;
+ pCtx->idxFmtHTML = 0;
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ pCtx->idxFmtURI = 0;
+#endif
+}
+
+/**
+ * Tells VBox that X11 currently has nothing in its clipboard.
+ *
+ * @param pCtx The X11 clipboard context to use.
+ */
+SHCL_X11_DECL(void) clipReportEmpty(PSHCLX11CTX pCtx)
+{
+ clipResetX11Formats(pCtx);
+ clipReportFormatsToVBox(pCtx);
+}
+
+/**
+ * Go through an array of X11 clipboard targets to see if they contain a text
+ * format we can support, and if so choose the ones we prefer (e.g. we like
+ * UTF-8 better than plain text).
+ *
+ * @return Index to supported X clipboard format.
+ * @param pCtx The X11 clipboard context to use.
+ * @param paIdxFmtTargets The list of targets.
+ * @param cTargets The size of the list in @a pTargets.
+ */
+SHCL_X11_DECL(SHCLX11FMTIDX) clipGetTextFormatFromTargets(PSHCLX11CTX pCtx,
+ SHCLX11FMTIDX *paIdxFmtTargets,
+ size_t cTargets)
+{
+ AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT);
+ AssertReturn(RT_VALID_PTR(paIdxFmtTargets) || cTargets == 0, NIL_CLIPX11FORMAT);
+
+ SHCLX11FMTIDX idxFmtText = NIL_CLIPX11FORMAT;
+ SHCLX11FMT fmtTextX11 = SHCLX11FMT_INVALID;
+ for (unsigned i = 0; i < cTargets; ++i)
+ {
+ SHCLX11FMTIDX idxFmt = paIdxFmtTargets[i];
+ if (idxFmt != NIL_CLIPX11FORMAT)
+ {
+ if ( (clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_UNICODETEXT)
+ && fmtTextX11 < clipRealFormatForX11Format(idxFmt))
+ {
+ fmtTextX11 = clipRealFormatForX11Format(idxFmt);
+ idxFmtText = idxFmt;
+ }
+ }
+ }
+ return idxFmtText;
+}
+
+/**
+ * Goes through an array of X11 clipboard targets to see if they contain a bitmap
+ * format we can support, and if so choose the ones we prefer (e.g. we like
+ * BMP better than PNG because we don't have to convert).
+ *
+ * @return Supported X clipboard format.
+ * @param pCtx The X11 clipboard context to use.
+ * @param paIdxFmtTargets The list of targets.
+ * @param cTargets The size of the list in @a pTargets.
+ */
+static SHCLX11FMTIDX clipGetBitmapFormatFromTargets(PSHCLX11CTX pCtx,
+ SHCLX11FMTIDX *paIdxFmtTargets,
+ size_t cTargets)
+{
+ AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT);
+ AssertReturn(RT_VALID_PTR(paIdxFmtTargets) || cTargets == 0, NIL_CLIPX11FORMAT);
+
+ SHCLX11FMTIDX idxFmtBmp = NIL_CLIPX11FORMAT;
+ SHCLX11FMT fmtBmpX11 = SHCLX11FMT_INVALID;
+ for (unsigned i = 0; i < cTargets; ++i)
+ {
+ SHCLX11FMTIDX idxFmt = paIdxFmtTargets[i];
+ if (idxFmt != NIL_CLIPX11FORMAT)
+ {
+ if ( (clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_BITMAP)
+ && fmtBmpX11 < clipRealFormatForX11Format(idxFmt))
+ {
+ fmtBmpX11 = clipRealFormatForX11Format(idxFmt);
+ idxFmtBmp = idxFmt;
+ }
+ }
+ }
+ return idxFmtBmp;
+}
+
+/**
+ * Goes through an array of X11 clipboard targets to see if they contain a HTML
+ * format we can support, and if so choose the ones we prefer.
+ *
+ * @return Supported X clipboard format.
+ * @param pCtx The X11 clipboard context to use.
+ * @param paIdxFmtTargets The list of targets.
+ * @param cTargets The size of the list in @a pTargets.
+ */
+static SHCLX11FMTIDX clipGetHtmlFormatFromTargets(PSHCLX11CTX pCtx,
+ SHCLX11FMTIDX *paIdxFmtTargets,
+ size_t cTargets)
+{
+ AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT);
+ AssertReturn(RT_VALID_PTR(paIdxFmtTargets) || cTargets == 0, NIL_CLIPX11FORMAT);
+
+ SHCLX11FMTIDX idxFmtHTML = NIL_CLIPX11FORMAT;
+ SHCLX11FMT fmxHTMLX11 = SHCLX11FMT_INVALID;
+ for (unsigned i = 0; i < cTargets; ++i)
+ {
+ SHCLX11FMTIDX idxFmt = paIdxFmtTargets[i];
+ if (idxFmt != NIL_CLIPX11FORMAT)
+ {
+ if ( (clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_HTML)
+ && fmxHTMLX11 < clipRealFormatForX11Format(idxFmt))
+ {
+ fmxHTMLX11 = clipRealFormatForX11Format(idxFmt);
+ idxFmtHTML = idxFmt;
+ }
+ }
+ }
+ return idxFmtHTML;
+}
+
+# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+/**
+ * Goes through an array of X11 clipboard targets to see if they contain an URI list
+ * format we can support, and if so choose the ones we prefer.
+ *
+ * @return Supported X clipboard format.
+ * @param pCtx The X11 clipboard context to use.
+ * @param paIdxFmtTargets The list of targets.
+ * @param cTargets The size of the list in @a pTargets.
+ */
+static SHCLX11FMTIDX clipGetURIListFormatFromTargets(PSHCLX11CTX pCtx,
+ SHCLX11FMTIDX *paIdxFmtTargets,
+ size_t cTargets)
+{
+ AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT);
+ AssertReturn(RT_VALID_PTR(paIdxFmtTargets) || cTargets == 0, NIL_CLIPX11FORMAT);
+
+ SHCLX11FMTIDX idxFmtURI = NIL_CLIPX11FORMAT;
+ SHCLX11FMT fmtURIX11 = SHCLX11FMT_INVALID;
+ for (unsigned i = 0; i < cTargets; ++i)
+ {
+ SHCLX11FMTIDX idxFmt = paIdxFmtTargets[i];
+ if (idxFmt != NIL_CLIPX11FORMAT)
+ {
+ if ( (clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_URI_LIST)
+ && fmtURIX11 < clipRealFormatForX11Format(idxFmt))
+ {
+ fmtURIX11 = clipRealFormatForX11Format(idxFmt);
+ idxFmtURI = idxFmt;
+ }
+ }
+ }
+ return idxFmtURI;
+}
+# endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
+
+/**
+ * Goes through an array of X11 clipboard targets to see if we can support any
+ * of them and if relevant to choose the ones we prefer (e.g. we like Utf8
+ * better than plain text).
+ *
+ * @param pCtx The X11 clipboard context to use.
+ * @param paIdxFmtTargets The list of targets.
+ * @param cTargets The size of the list in @a pTargets.
+ */
+static void clipGetFormatsFromTargets(PSHCLX11CTX pCtx,
+ SHCLX11FMTIDX *paIdxFmtTargets, size_t cTargets)
+{
+ AssertPtrReturnVoid(pCtx);
+ AssertPtrReturnVoid(paIdxFmtTargets);
+
+ SHCLX11FMTIDX idxFmtText = clipGetTextFormatFromTargets(pCtx, paIdxFmtTargets, cTargets);
+ if (pCtx->idxFmtText != idxFmtText)
+ pCtx->idxFmtText = idxFmtText;
+
+ pCtx->idxFmtBmp = SHCLX11FMT_INVALID; /* not yet supported */ /** @todo r=andy Check this. */
+ SHCLX11FMTIDX idxFmtBmp = clipGetBitmapFormatFromTargets(pCtx, paIdxFmtTargets, cTargets);
+ if (pCtx->idxFmtBmp != idxFmtBmp)
+ pCtx->idxFmtBmp = idxFmtBmp;
+
+ SHCLX11FMTIDX idxFmtHTML = clipGetHtmlFormatFromTargets(pCtx, paIdxFmtTargets, cTargets);
+ if (pCtx->idxFmtHTML != idxFmtHTML)
+ pCtx->idxFmtHTML = idxFmtHTML;
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ SHCLX11FMTIDX idxFmtURI = clipGetURIListFormatFromTargets(pCtx, paIdxFmtTargets, cTargets);
+ if (pCtx->idxFmtURI != idxFmtURI)
+ pCtx->idxFmtURI = idxFmtURI;
+#endif
+}
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY
+DECLINLINE(bool) clipGetXtBusy(PSHCLX11CTX pCtx)
+{
+ LogFlowFunc(("fXtBusy=%RTbool, fXtNeedsUpdate=%RTbool\n", pCtx->fXtBusy, pCtx->fXtNeedsUpdate));
+ return pCtx->fXtBusy;
+}
+
+DECLINLINE(bool) clipGetXtNeedsUpdate(PSHCLX11CTX pCtx)
+{
+ LogFlowFunc(("fXtBusy=%RTbool, fXtNeedsUpdate=%RTbool\n", pCtx->fXtBusy, pCtx->fXtNeedsUpdate));
+ return pCtx->fXtNeedsUpdate;
+}
+
+DECLINLINE(bool) clipSetXtBusy(PSHCLX11CTX pCtx, bool fBusy)
+{
+ pCtx->fXtBusy = fBusy;
+ LogFlowFunc(("fXtBusy=%RTbool, fXtNeedsUpdate=%RTbool\n", pCtx->fXtBusy, pCtx->fXtNeedsUpdate));
+ return pCtx->fXtBusy;
+}
+
+DECLINLINE(bool) clipSetXtNeedsUpdate(PSHCLX11CTX pCtx, bool fNeedsUpdate)
+{
+ pCtx->fXtNeedsUpdate = fNeedsUpdate;
+ LogFlowFunc(("fXtBusy=%RTbool, fXtNeedsUpdate=%RTbool\n", pCtx->fXtBusy, pCtx->fXtNeedsUpdate));
+ return pCtx->fXtNeedsUpdate;
+}
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY */
+
+/**
+ * Updates the context's information about targets currently supported by X11,
+ * based on an array of X11 atoms.
+ *
+ * @param pCtx The X11 clipboard context to use.
+ * @param pTargets The array of atoms describing the targets supported.
+ * @param cTargets The size of the array @a pTargets.
+ */
+SHCL_X11_DECL(void) clipUpdateX11Targets(PSHCLX11CTX pCtx, SHCLX11FMTIDX *paIdxFmtTargets, size_t cTargets)
+{
+ LogFlowFuncEnter();
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY
+ clipSetXtBusy(pCtx, false);
+ if (clipGetXtNeedsUpdate(pCtx))
+ {
+ /* We may already be out of date. */
+ clipSetXtNeedsUpdate(pCtx, false);
+ clipQueryX11Targets(pCtx);
+ return;
+ }
+#endif
+
+ if (paIdxFmtTargets == NULL)
+ {
+ /* No data available */
+ clipReportEmpty(pCtx);
+ return;
+ }
+
+ clipGetFormatsFromTargets(pCtx, paIdxFmtTargets, cTargets);
+ clipReportFormatsToVBox(pCtx);
+}
+
+/**
+ * Notifies the VBox clipboard about available data formats ("targets" on X11),
+ * based on the information obtained from the X11 clipboard.
+ *
+ * @note Callback installed by clipQueryX11Targets() for XtGetSelectionValue().
+ * @note This function is treated as API glue, and as such is not part of any
+ * unit test. So keep it simple, be paranoid and log everything.
+ */
+SHCL_X11_DECL(void) clipQueryX11TargetsCallback(Widget widget, XtPointer pClient,
+ Atom * /* selection */, Atom *atomType,
+ XtPointer pValue, long unsigned int *pcLen,
+ int *piFormat)
+{
+ RT_NOREF(piFormat);
+
+ PSHCLX11CTX pCtx = reinterpret_cast<SHCLX11CTX *>(pClient);
+
+ LogFlowFunc(("pValue=%p, *pcLen=%u, *atomType=%d%s\n",
+ pValue, *pcLen, *atomType, *atomType == XT_CONVERT_FAIL ? " (XT_CONVERT_FAIL)" : ""));
+
+ Atom *pAtoms = (Atom *)pValue;
+
+ unsigned cFormats = *pcLen;
+
+ LogRel2(("Shared Clipboard: Querying X11 formats ...\n"));
+ LogRel2(("Shared Clipboard: %u X11 formats were found\n", cFormats));
+
+ SHCLX11FMTIDX *paIdxFmt = NULL;
+ if ( cFormats
+ && pValue
+ && (*atomType != XT_CONVERT_FAIL /* time out */))
+ {
+ /* Allocated array to hold the format indices. */
+ paIdxFmt = (SHCLX11FMTIDX *)RTMemAllocZ(cFormats * sizeof(SHCLX11FMTIDX));
+ }
+
+#if !defined(TESTCASE)
+ if (pValue)
+ {
+ for (unsigned i = 0; i < cFormats; ++i)
+ {
+ if (pAtoms[i])
+ {
+ char *pszName = XGetAtomName(XtDisplay(widget), pAtoms[i]);
+ LogRel2(("Shared Clipboard: Found X11 format '%s'\n", pszName));
+ XFree(pszName);
+ }
+ else
+ LogFunc(("Found empty target\n"));
+ }
+ }
+#endif
+
+ if (paIdxFmt)
+ {
+ for (unsigned i = 0; i < cFormats; ++i)
+ {
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aFormats); ++j)
+ {
+ Atom target = XInternAtom(XtDisplay(widget),
+ g_aFormats[j].pcszAtom, False);
+ if (*(pAtoms + i) == target)
+ paIdxFmt[i] = j;
+ }
+#if !defined(TESTCASE)
+ if (paIdxFmt[i] != SHCLX11FMT_INVALID)
+ LogRel2(("Shared Clipboard: Reporting X11 format '%s'\n", g_aFormats[paIdxFmt[i]].pcszAtom));
+#endif
+ }
+ }
+ else
+ LogFunc(("Reporting empty targets (none reported or allocation failure)\n"));
+
+ clipUpdateX11Targets(pCtx, paIdxFmt, cFormats);
+ RTMemFree(paIdxFmt);
+
+ XtFree(reinterpret_cast<char *>(pValue));
+}
+
+/**
+ * Queries the current formats ("targets") of the X11 clipboard ("CLIPBOARD").
+ *
+ * @param pCtx The X11 clipboard context to use.
+ */
+SHCL_X11_DECL(void) clipQueryX11Targets(PSHCLX11CTX pCtx)
+{
+#ifndef TESTCASE
+
+# ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY
+ if (clipGetXtBusy(pCtx))
+ {
+ clipSetXtNeedsUpdate(pCtx, true);
+ return;
+ }
+ clipSetXtBusy(pCtx, true);
+# endif
+
+ XtGetSelectionValue(pCtx->pWidget,
+ clipGetAtom(pCtx, "CLIPBOARD"),
+ clipGetAtom(pCtx, "TARGETS"),
+ clipQueryX11TargetsCallback, pCtx,
+ CurrentTime);
+#else
+ tstRequestTargets(pCtx);
+#endif
+}
+
+typedef struct
+{
+ int type; /* event base */
+ unsigned long serial;
+ Bool send_event;
+ Display *display;
+ Window window;
+ int subtype;
+ Window owner;
+ Atom selection;
+ Time timestamp;
+ Time selection_timestamp;
+} XFixesSelectionNotifyEvent;
+
+#ifndef TESTCASE
+/**
+ * Waits until an event arrives and handle it if it is an XFIXES selection
+ * event, which Xt doesn't know about.
+ *
+ * @param pCtx The X11 clipboard context to use.
+ */
+static void clipPeekEventAndDoXFixesHandling(PSHCLX11CTX pCtx)
+{
+ union
+ {
+ XEvent event;
+ XFixesSelectionNotifyEvent fixes;
+ } event = { { 0 } };
+
+ if (XtAppPeekEvent(pCtx->pAppContext, &event.event))
+ {
+ if ( (event.event.type == pCtx->fixesEventBase)
+ && (event.fixes.owner != XtWindow(pCtx->pWidget)))
+ {
+ if ( (event.fixes.subtype == 0 /* XFixesSetSelectionOwnerNotify */)
+ && (event.fixes.owner != 0))
+ clipQueryX11Targets(pCtx);
+ else
+ clipReportEmpty(pCtx);
+ }
+ }
+}
+
+/**
+ * The main loop of our X11 event thread.
+ *
+ * @returns VBox status code.
+ * @param hThreadSelf Associated thread handle.
+ * @param pvUser Pointer to the X11 clipboard context to use.
+ */
+static DECLCALLBACK(int) clipThreadMain(RTTHREAD hThreadSelf, void *pvUser)
+{
+ PSHCLX11CTX pCtx = (PSHCLX11CTX)pvUser;
+ AssertPtr(pCtx);
+
+ LogFlowFunc(("pCtx=%p\n", pCtx));
+
+ bool fSignalled = false; /* Whether we have signalled the parent already or not. */
+
+ int rc = clipInitInternal(pCtx);
+ if (RT_SUCCESS(rc))
+ {
+ rc = clipRegisterContext(pCtx);
+ if (RT_SUCCESS(rc))
+ {
+ if (pCtx->fGrabClipboardOnStart)
+ clipQueryX11Targets(pCtx);
+
+ pCtx->fThreadStarted = true;
+
+ /* We're now ready to run, tell parent. */
+ int rc2 = RTThreadUserSignal(hThreadSelf);
+ AssertRC(rc2);
+
+ fSignalled = true;
+
+ while (XtAppGetExitFlag(pCtx->pAppContext) == FALSE)
+ {
+ clipPeekEventAndDoXFixesHandling(pCtx);
+ XtAppProcessEvent(pCtx->pAppContext, XtIMAll);
+ }
+
+ LogRel(("Shared Clipboard: X11 event thread exiting\n"));
+
+ clipUnregisterContext(pCtx);
+ }
+ else
+ {
+ LogRel(("Shared Clipboard: unable to register clip context: %Rrc\n", rc));
+ }
+
+ clipUninitInternal(pCtx);
+ }
+
+ if (!fSignalled) /* Signal parent if we didn't do so yet. */
+ {
+ int rc2 = RTThreadUserSignal(hThreadSelf);
+ AssertRC(rc2);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Worker function for stopping the clipboard which runs on the event
+ * thread.
+ *
+ * @param pvUserData Pointer to the X11 clipboard context to use.
+ */
+static void clipThreadSignalStop(void *pvUserData, void *)
+{
+ PSHCLX11CTX pCtx = (PSHCLX11CTX)pvUserData;
+
+ /* This might mean that we are getting stopped twice. */
+ Assert(pCtx->pWidget != NULL);
+
+ /* Set the termination flag to tell the Xt event loop to exit. We
+ * reiterate that any outstanding requests from the X11 event loop to
+ * the VBox part *must* have returned before we do this. */
+ XtAppSetExitFlag(pCtx->pAppContext);
+}
+
+/**
+ * Sets up the XFixes library and load the XFixesSelectSelectionInput symbol.
+ */
+static int clipLoadXFixes(Display *pDisplay, PSHCLX11CTX pCtx)
+{
+ int rc;
+
+ void *hFixesLib = dlopen("libXfixes.so.1", RTLD_LAZY);
+ if (!hFixesLib)
+ hFixesLib = dlopen("libXfixes.so.2", RTLD_LAZY);
+ if (!hFixesLib)
+ hFixesLib = dlopen("libXfixes.so.3", RTLD_LAZY);
+ if (!hFixesLib)
+ hFixesLib = dlopen("libXfixes.so.4", RTLD_LAZY);
+ if (hFixesLib)
+ {
+ /* For us, a NULL function pointer is a failure */
+ pCtx->fixesSelectInput = (void (*)(Display *, Window, Atom, long unsigned int))
+ (uintptr_t)dlsym(hFixesLib, "XFixesSelectSelectionInput");
+ if (pCtx->fixesSelectInput)
+ {
+ int dummy1 = 0;
+ int dummy2 = 0;
+ if (XQueryExtension(pDisplay, "XFIXES", &dummy1, &pCtx->fixesEventBase, &dummy2) != 0)
+ {
+ if (pCtx->fixesEventBase >= 0)
+ {
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ LogRel(("Shared Clipboard: fixesEventBase is less than zero: %d\n", pCtx->fixesEventBase));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ {
+ LogRel(("Shared Clipboard: XQueryExtension failed\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ {
+ LogRel(("Shared Clipboard: Symbol XFixesSelectSelectionInput not found!\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ {
+ LogRel(("Shared Clipboard: libxFixes.so.* not found!\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ return rc;
+}
+
+/**
+ * This is the callback which is scheduled when data is available on the
+ * wakeup pipe. It simply reads all data from the pipe.
+ *
+ * @param pvUserData Pointer to the X11 clipboard context to use.
+ */
+static void clipThreadDrainWakeupPipe(XtPointer pvUserData, int *, XtInputId *)
+{
+ LogFlowFuncEnter();
+
+ PSHCLX11CTX pCtx = (PSHCLX11CTX)pvUserData;
+ char acBuf[WAKE_UP_STRING_LEN];
+
+ while (read(pCtx->wakeupPipeRead, acBuf, sizeof(acBuf)) > 0) {}
+}
+#endif /* !TESTCASE */
+
+/**
+ * X11-specific initialisation for the Shared Clipboard.
+ *
+ * Note: Must be called from the thread serving the Xt stuff.
+ *
+ * @returns VBox status code.
+ * @param pCtx The X11 clipboard context to init.
+ */
+static int clipInitInternal(PSHCLX11CTX pCtx)
+{
+ LogFlowFunc(("pCtx=%p\n", pCtx));
+
+ /* Make sure we are thread safe. */
+ XtToolkitThreadInitialize();
+
+ /*
+ * Set up the Clipboard application context and main window. We call all
+ * these functions directly instead of calling XtOpenApplication() so
+ * that we can fail gracefully if we can't get an X11 display.
+ */
+ XtToolkitInitialize();
+
+ int rc = VINF_SUCCESS;
+
+ Assert(pCtx->pAppContext == NULL); /* No nested initialization. */
+ pCtx->pAppContext = XtCreateApplicationContext();
+ if (pCtx->pAppContext == NULL)
+ {
+ LogRel(("Shared Clipboard: Failed to create Xt application context\n"));
+ return VERR_NOT_SUPPORTED; /** @todo Fudge! */
+ }
+
+ /* Create a window and make it a clipboard viewer. */
+ int cArgc = 0;
+ char *pcArgv = 0;
+ Display *pDisplay = XtOpenDisplay(pCtx->pAppContext, 0, 0, "VBoxShCl", 0, 0, &cArgc, &pcArgv);
+ if (pDisplay == NULL)
+ {
+ LogRel(("Shared Clipboard: Failed to connect to the X11 clipboard - the window system may not be running\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+#ifndef TESTCASE
+ if (RT_SUCCESS(rc))
+ {
+ rc = clipLoadXFixes(pDisplay, pCtx);
+ if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: Failed to load the XFIXES extension\n"));
+ }
+#endif
+
+ if (RT_SUCCESS(rc))
+ {
+ pCtx->pWidget = XtVaAppCreateShell(0, "VBoxShCl",
+ applicationShellWidgetClass,
+ pDisplay, XtNwidth, 1, XtNheight,
+ 1, NULL);
+ if (pCtx->pWidget == NULL)
+ {
+ LogRel(("Shared Clipboard: Failed to create Xt app shell\n"));
+ rc = VERR_NO_MEMORY; /** @todo r=andy Improve this. */
+ }
+ else
+ {
+#ifndef TESTCASE
+ if (!XtAppAddInput(pCtx->pAppContext, pCtx->wakeupPipeRead,
+ (XtPointer) XtInputReadMask,
+ clipThreadDrainWakeupPipe, (XtPointer) pCtx))
+ {
+ LogRel(("Shared Clipboard: Failed to add input to Xt app context\n"));
+ rc = VERR_ACCESS_DENIED; /** @todo r=andy Improve this. */
+ }
+#endif
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ XtSetMappedWhenManaged(pCtx->pWidget, false);
+ XtRealizeWidget(pCtx->pWidget);
+
+#ifndef TESTCASE
+ /* Enable clipboard update notification. */
+ pCtx->fixesSelectInput(pDisplay, XtWindow(pCtx->pWidget),
+ clipGetAtom(pCtx, "CLIPBOARD"),
+ 7 /* All XFixes*Selection*NotifyMask flags */);
+#endif
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Shared Clipboard: Initialisation failed: %Rrc\n", rc));
+ clipUninitInternal(pCtx);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * X11-specific uninitialisation for the Shared Clipboard.
+ *
+ * Note: Must be called from the thread serving the Xt stuff.
+ *
+ * @param pCtx The X11 clipboard context to uninit.
+ */
+static void clipUninitInternal(PSHCLX11CTX pCtx)
+{
+ AssertPtrReturnVoid(pCtx);
+
+ LogFlowFunc(("pCtx=%p\n", pCtx));
+
+ if (pCtx->pWidget)
+ {
+ /* Valid widget + invalid appcontext = bug. But don't return yet. */
+ AssertPtr(pCtx->pAppContext);
+
+ XtDestroyWidget(pCtx->pWidget);
+ pCtx->pWidget = NULL;
+ }
+
+ if (pCtx->pAppContext)
+ {
+ XtDestroyApplicationContext(pCtx->pAppContext);
+ pCtx->pAppContext = NULL;
+ }
+
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+}
+
+/**
+ * Sets the callback table, internal version.
+ *
+ * @param pCtx The clipboard context.
+ * @param pCallbacks Callback table to set. If NULL, the current callback table will be cleared.
+ */
+static void shClX11SetCallbacksInternal(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks)
+{
+ if (pCallbacks)
+ {
+ memcpy(&pCtx->Callbacks, pCallbacks, sizeof(SHCLCALLBACKS));
+ }
+ else
+ RT_ZERO(pCtx->Callbacks);
+}
+
+/**
+ * Sets the callback table.
+ *
+ * @param pCtx The clipboard context.
+ * @param pCallbacks Callback table to set. If NULL, the current callback table will be cleared.
+ */
+void ShClX11SetCallbacks(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks)
+{
+ shClX11SetCallbacksInternal(pCtx, pCallbacks);
+}
+
+/**
+ * Initializes a X11 context of the Shared Clipboard.
+ *
+ * @returns VBox status code.
+ * @param pCtx The clipboard context to initialize.
+ * @param pCallbacks Callback table to use.
+ * @param pParent Parent context to use.
+ * @param fHeadless Whether the code runs in a headless environment or not.
+ */
+int ShClX11Init(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks, PSHCLCONTEXT pParent, bool fHeadless)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pCtx=%p\n", pCtx));
+
+ int rc = VINF_SUCCESS;
+
+ RT_BZERO(pCtx, sizeof(SHCLX11CTX));
+
+ if (fHeadless)
+ {
+ /*
+ * If we don't find the DISPLAY environment variable we assume that
+ * we are not connected to an X11 server. Don't actually try to do
+ * this then, just fail silently and report success on every call.
+ * This is important for VBoxHeadless.
+ */
+ LogRel(("Shared Clipboard: X11 DISPLAY variable not set -- disabling clipboard sharing\n"));
+ }
+
+ /* Install given callbacks. */
+ shClX11SetCallbacksInternal(pCtx, pCallbacks);
+
+ pCtx->fHaveX11 = !fHeadless;
+ pCtx->pFrontend = pParent;
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY
+ pCtx->fXtBusy = false;
+ pCtx->fXtNeedsUpdate = false;
+#endif
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
+ ShClTransferHttpServerInit(&pCtx->HttpCtx.HttpServer);
+#endif
+
+#ifdef TESTCASE
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo The testcases currently do not utilize the threading code. So init stuff here. */
+ rc = clipInitInternal(pCtx);
+ if (RT_SUCCESS(rc))
+ rc = clipRegisterContext(pCtx);
+ }
+#endif
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Destroys a Shared Clipboard X11 context.
+ *
+ * @param pCtx The X11 clipboard context to destroy.
+ */
+void ShClX11Destroy(PSHCLX11CTX pCtx)
+{
+ if (!pCtx)
+ return;
+
+ LogFlowFunc(("pCtx=%p\n", pCtx));
+
+#ifdef TESTCASE
+ /** @todo The testcases currently do not utilize the threading code. So uninit stuff here. */
+ clipUnregisterContext(pCtx);
+ clipUninitInternal(pCtx);
+#endif
+
+ if (pCtx->fHaveX11)
+ {
+ /* We set this to NULL when the event thread exits. It really should
+ * have exited at this point, when we are about to unload the code from
+ * memory. */
+ Assert(pCtx->pWidget == NULL);
+ }
+}
+
+#ifndef TESTCASE
+/**
+ * Starts our own Xt even thread for handling Shared Clipboard messages, extended version.
+ *
+ * @returns VBox status code.
+ * @param pCtx The X11 clipboard context to use.
+ * @param pszName Thread name to use.
+ * @param fGrab Whether we should try to grab the shared clipboard at once.
+ */
+int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Immediately return if we are not connected to the X server.
+ */
+ if (!pCtx->fHaveX11)
+ return VINF_SUCCESS;
+
+ pCtx->fGrabClipboardOnStart = fGrab;
+
+ clipResetX11Formats(pCtx);
+
+ int rc;
+
+ /*
+ * Create the pipes.
+ ** @todo r=andy Replace this with RTPipe API.
+ */
+ int pipes[2];
+ if (!pipe(pipes))
+ {
+ pCtx->wakeupPipeRead = pipes[0];
+ pCtx->wakeupPipeWrite = pipes[1];
+
+ if (!fcntl(pCtx->wakeupPipeRead, F_SETFL, O_NONBLOCK))
+ {
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+
+ if (RT_SUCCESS(rc))
+ {
+ LogRel2(("Shared Clipboard: Starting X11 event thread ...\n"));
+
+ rc = RTThreadCreate(&pCtx->Thread, clipThreadMain, pCtx, 0,
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, pszName);
+ if (RT_SUCCESS(rc))
+ rc = RTThreadUserWait(pCtx->Thread, RT_MS_30SEC /* msTimeout */);
+
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Shared Clipboard: Failed to start the X11 event thread with %Rrc\n", rc));
+ clipUninitInternal(pCtx);
+ }
+ else
+ {
+ if (!pCtx->fThreadStarted)
+ {
+ LogRel(("Shared Clipboard: X11 event thread reported an error while starting\n"));
+ }
+ else
+ LogRel2(("Shared Clipboard: X11 event thread started\n"));
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Starts our own Xt even thread for handling Shared Clipboard messages.
+ *
+ * @returns VBox status code.
+ * @param pCtx The X11 clipboard context to use.
+ * @param fGrab Whether we should try to grab the shared clipboard at once.
+ */
+int ShClX11ThreadStart(PSHCLX11CTX pCtx, bool fGrab)
+{
+ return ShClX11ThreadStartEx(pCtx, "SHCLX11", fGrab);
+}
+
+/**
+ * Stops the Shared Clipboard Xt even thread.
+ *
+ * @note Any requests from this object to get clipboard data from VBox
+ * *must* have completed or aborted before we are called, as
+ * otherwise the X11 event loop will still be waiting for the request
+ * to return and will not be able to terminate.
+ *
+ * @returns VBox status code.
+ * @param pCtx The X11 clipboard context to use.
+ */
+int ShClX11ThreadStop(PSHCLX11CTX pCtx)
+{
+ int rc;
+ /*
+ * Immediately return if we are not connected to the X server.
+ */
+ if (!pCtx->fHaveX11)
+ return VINF_SUCCESS;
+
+ LogRel2(("Shared Clipboard: Signalling the X11 event thread to stop\n"));
+
+ /* Write to the "stop" pipe. */
+ rc = clipThreadScheduleCall(pCtx, clipThreadSignalStop, (XtPointer)pCtx);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Shared Clipboard: cannot notify X11 event thread on shutdown with %Rrc\n", rc));
+ return rc;
+ }
+
+ LogRel2(("Shared Clipboard: Waiting for X11 event thread to stop ...\n"));
+
+ int rcThread;
+ rc = RTThreadWait(pCtx->Thread, RT_MS_30SEC /* msTimeout */, &rcThread);
+ if (RT_SUCCESS(rc))
+ rc = rcThread;
+ if (RT_SUCCESS(rc))
+ {
+ if (pCtx->wakeupPipeRead != 0)
+ {
+ close(pCtx->wakeupPipeRead);
+ pCtx->wakeupPipeRead = 0;
+ }
+
+ if (pCtx->wakeupPipeWrite != 0)
+ {
+ close(pCtx->wakeupPipeWrite);
+ pCtx->wakeupPipeWrite = 0;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ LogRel2(("Shared Clipboard: X11 event thread stopped successfully\n"));
+ }
+ else
+ LogRel(("Shared Clipboard: Stopping X11 event thread failed with %Rrc\n", rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+#endif /* !TESTCASE */
+
+/**
+ * Returns the targets supported by VBox.
+ *
+ * This will return a list of atoms which tells the caller
+ * what kind of clipboard formats we support.
+ *
+ * @returns VBox status code.
+ * @param pCtx The X11 clipboard context to use.
+ * @param atomTypeReturn The type of the data we are returning.
+ * @param pValReturn A pointer to the data we are returning. This
+ * should be set to memory allocated by XtMalloc,
+ * which will be freed later by the Xt toolkit.
+ * @param pcLenReturn The length of the data we are returning.
+ * @param piFormatReturn The format (8bit, 16bit, 32bit) of the data we are
+ * returning.
+ * @note X11 backend code, called by the XtOwnSelection callback.
+ */
+static int clipCreateX11Targets(PSHCLX11CTX pCtx, Atom *atomTypeReturn,
+ XtPointer *pValReturn,
+ unsigned long *pcLenReturn,
+ int *piFormatReturn)
+{
+ const unsigned cFixedTargets = 3; /* See below. */
+
+ Atom *pAtomTargets = (Atom *)XtMalloc((SHCL_MAX_X11_FORMATS + cFixedTargets) * sizeof(Atom));
+ if (!pAtomTargets)
+ return VERR_NO_MEMORY;
+
+ unsigned cTargets = 0;
+ SHCLX11FMTIDX idxFmt = NIL_CLIPX11FORMAT;
+ do
+ {
+ idxFmt = clipEnumX11Formats(pCtx->vboxFormats, idxFmt);
+ if (idxFmt != NIL_CLIPX11FORMAT)
+ {
+ pAtomTargets[cTargets] = clipAtomForX11Format(pCtx, idxFmt);
+ ++cTargets;
+ }
+ } while (idxFmt != NIL_CLIPX11FORMAT);
+
+ /* We always offer these fixed targets. */
+ pAtomTargets[cTargets] = clipGetAtom(pCtx, "TARGETS");
+ pAtomTargets[cTargets + 1] = clipGetAtom(pCtx, "MULTIPLE");
+ pAtomTargets[cTargets + 2] = clipGetAtom(pCtx, "TIMESTAMP");
+
+ *atomTypeReturn = XA_ATOM;
+ *pValReturn = (XtPointer)pAtomTargets;
+ *pcLenReturn = cTargets + cFixedTargets;
+ *piFormatReturn = 32;
+
+ LogFlowFunc(("cTargets=%u\n", cTargets + cFixedTargets));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Helper for ShClX11RequestDataForX11Callback() that will cache the data returned.
+ *
+ * @returns VBox status code. VERR_NO_DATA if no data available.
+ * @param pCtx The X11 clipboard context to use.
+ * @param uFmt Clipboard format to read data in.
+ * @param ppv Returns an allocated buffer with data read on success.
+ * Needs to be free'd with RTMemFree() by the caller.
+ * @param pcb Returns the amount of data read (in bytes) on success.
+ */
+static int shClX11RequestDataForX11CallbackHelper(PSHCLX11CTX pCtx, SHCLFORMAT uFmt,
+ void **ppv, uint32_t *pcb)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppv, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcb, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pCtx=%p, uFmt=%#x\n", pCtx, uFmt));
+
+ int rc = VINF_SUCCESS;
+
+ void *pv = NULL;
+ uint32_t cb = 0;
+
+ if (uFmt == VBOX_SHCL_FMT_UNICODETEXT)
+ {
+ if (pCtx->pvUnicodeCache == NULL) /** @todo r=andy Using string cache here? */
+ rc = pCtx->Callbacks.pfnOnRequestDataFromSource(pCtx->pFrontend, uFmt, &pCtx->pvUnicodeCache, &pCtx->cbUnicodeCache,
+ NULL /* pvUser */);
+ if ( RT_SUCCESS(rc)
+ /* Catch misbehaving callbacks. */
+ && pCtx->pvUnicodeCache
+ && pCtx->cbUnicodeCache)
+ {
+ pv = RTMemDup(pCtx->pvUnicodeCache, pCtx->cbUnicodeCache);
+ if (pv)
+ cb = pCtx->cbUnicodeCache;
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = pCtx->Callbacks.pfnOnRequestDataFromSource(pCtx->pFrontend, uFmt, &pv, &cb, NULL /* pvUser */);
+
+
+ /* Safey net in case the callbacks above misbehave
+ * (must return VERR_NO_DATA if no data available). */
+ if ( RT_SUCCESS(rc)
+ && (pv == NULL || cb == 0))
+ rc = VERR_NO_DATA;
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppv = pv;
+ *pcb = cb;
+ }
+
+ LogFlowFunc(("Returning pv=%p, cb=%RU32, rc=%Rrc\n", pv, cb, rc));
+ return rc;
+}
+
+/**
+ * Satisfies a request from X11 to convert the clipboard text to UTF-8 LF.
+ *
+ * @returns VBox status code. VERR_NO_DATA if no data was converted.
+ * @param pDisplay An X11 display structure, needed for conversions
+ * performed by Xlib.
+ * @param pv The text to be converted (UCS-2 with Windows EOLs).
+ * @param cb The length of the text in @cb in bytes.
+ * @param atomTypeReturn Where to store the atom for the type of the data
+ * we are returning.
+ * @param pValReturn Where to store the pointer to the data we are
+ * returning. This should be to memory allocated by
+ * XtMalloc, which will be freed by the Xt toolkit
+ * later.
+ * @param pcLenReturn Where to store the length of the data we are
+ * returning.
+ * @param piFormatReturn Where to store the bit width (8, 16, 32) of the
+ * data we are returning.
+ */
+static int clipConvertUtf16ToX11Data(Display *pDisplay, PRTUTF16 pwszSrc,
+ size_t cbSrc, Atom *atomTarget,
+ Atom *atomTypeReturn,
+ XtPointer *pValReturn,
+ unsigned long *pcLenReturn,
+ int *piFormatReturn)
+{
+ RT_NOREF(pDisplay);
+ AssertReturn(cbSrc % sizeof(RTUTF16) == 0, VERR_INVALID_PARAMETER);
+
+ const size_t cwcSrc = cbSrc / sizeof(RTUTF16);
+ if (!cwcSrc)
+ return VERR_NO_DATA;
+
+ /* This may slightly overestimate the space needed. */
+ size_t chDst = 0;
+ int rc = ShClUtf16LenUtf8(pwszSrc, cwcSrc, &chDst);
+ if (RT_SUCCESS(rc))
+ {
+ chDst++; /* Add space for terminator. */
+
+ char *pszDst = (char *)XtMalloc(chDst);
+ if (pszDst)
+ {
+ size_t cbActual = 0;
+ rc = ShClConvUtf16CRLFToUtf8LF(pwszSrc, cwcSrc, pszDst, chDst, &cbActual);
+ if (RT_SUCCESS(rc))
+ {
+ *atomTypeReturn = *atomTarget;
+ *pValReturn = (XtPointer)pszDst;
+ *pcLenReturn = cbActual + 1 /* Include terminator */;
+ *piFormatReturn = 8;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Satisfies a request from X11 to convert the clipboard HTML fragment to UTF-8. We
+ * return null-terminated text, but can cope with non-null-terminated input.
+ *
+ * @returns VBox status code.
+ * @param pDisplay An X11 display structure, needed for conversions
+ * performed by Xlib.
+ * @param pv The text to be converted (UTF8 with Windows EOLs).
+ * @param cb The length of the text in @cb in bytes.
+ * @param atomTypeReturn Where to store the atom for the type of the data
+ * we are returning.
+ * @param pValReturn Where to store the pointer to the data we are
+ * returning. This should be to memory allocated by
+ * XtMalloc, which will be freed by the Xt toolkit later.
+ * @param pcLenReturn Where to store the length of the data we are returning.
+ * @param piFormatReturn Where to store the bit width (8, 16, 32) of the
+ * data we are returning.
+ */
+static int clipConvertHtmlToX11Data(Display *pDisplay, const char *pszSrc,
+ size_t cbSrc, Atom *atomTarget,
+ Atom *atomTypeReturn,
+ XtPointer *pValReturn,
+ unsigned long *pcLenReturn,
+ int *piFormatReturn)
+{
+ RT_NOREF(pDisplay, pValReturn);
+
+ /* This may slightly overestimate the space needed. */
+ LogFlowFunc(("Source: %s", pszSrc));
+
+ char *pszDest = (char *)XtMalloc(cbSrc);
+ if (pszDest == NULL)
+ return VERR_NO_MEMORY;
+
+ memcpy(pszDest, pszSrc, cbSrc);
+
+ *atomTypeReturn = *atomTarget;
+ *pValReturn = (XtPointer)pszDest;
+ *pcLenReturn = cbSrc;
+ *piFormatReturn = 8;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Does this atom correspond to one of the two selection types we support?
+ *
+ * @param pCtx The X11 clipboard context to use.
+ * @param selType The atom in question.
+ */
+static bool clipIsSupportedSelectionType(PSHCLX11CTX pCtx, Atom selType)
+{
+ return( (selType == clipGetAtom(pCtx, "CLIPBOARD"))
+ || (selType == clipGetAtom(pCtx, "PRIMARY")));
+}
+
+/**
+ * Removes a trailing nul character from a string by adjusting the string
+ * length. Some X11 applications don't like zero-terminated text...
+ *
+ * @param pText The text in question.
+ * @param pcText The length of the text, adjusted on return.
+ * @param format The format of the text.
+ */
+static void clipTrimTrailingNul(XtPointer pText, unsigned long *pcText,
+ SHCLX11FMT format)
+{
+ AssertPtrReturnVoid(pText);
+ AssertPtrReturnVoid(pcText);
+ AssertReturnVoid((format == SHCLX11FMT_UTF8) || (format == SHCLX11FMT_TEXT) || (format == SHCLX11FMT_HTML));
+
+ if (((char *)pText)[*pcText - 1] == '\0')
+ --(*pcText);
+}
+
+static int clipConvertToX11Data(PSHCLX11CTX pCtx, Atom *atomTarget,
+ Atom *atomTypeReturn,
+ XtPointer *pValReturn,
+ unsigned long *pcLenReturn,
+ int *piFormatReturn)
+{
+ int rc = VERR_NOT_SUPPORTED; /* Play safe by default. */
+
+ SHCLX11FMTIDX idxFmtX11 = clipFindX11FormatByAtom(pCtx, *atomTarget);
+ SHCLX11FMT fmtX11 = clipRealFormatForX11Format(idxFmtX11);
+
+ LogFlowFunc(("vboxFormats=0x%x, idxFmtX11=%u ('%s'), fmtX11=%u\n",
+ pCtx->vboxFormats, idxFmtX11, g_aFormats[idxFmtX11].pcszAtom, fmtX11));
+
+#ifdef LOG_ENABLED
+ char *pszFmts = ShClFormatsToStrA(pCtx->vboxFormats);
+ AssertPtrReturn(pszFmts, VERR_NO_MEMORY);
+ LogRel2(("Shared Clipboard: Converting VBox formats '%s' to '%s' for X11\n",
+ pszFmts, g_aFormats[idxFmtX11].pcszAtom));
+ RTStrFree(pszFmts);
+#endif
+
+ void *pv = NULL;
+ uint32_t cb = 0;
+
+ if ( ( (fmtX11 == SHCLX11FMT_UTF8)
+ || (fmtX11 == SHCLX11FMT_TEXT)
+ )
+ && (pCtx->vboxFormats & VBOX_SHCL_FMT_UNICODETEXT))
+ {
+ rc = shClX11RequestDataForX11CallbackHelper(pCtx, VBOX_SHCL_FMT_UNICODETEXT, &pv, &cb);
+ if ( RT_SUCCESS(rc)
+ && ( (fmtX11 == SHCLX11FMT_UTF8)
+ || (fmtX11 == SHCLX11FMT_TEXT)))
+ {
+ rc = clipConvertUtf16ToX11Data(XtDisplay(pCtx->pWidget),
+ (PRTUTF16)pv, cb, atomTarget,
+ atomTypeReturn, pValReturn,
+ pcLenReturn, piFormatReturn);
+ }
+
+ if (RT_SUCCESS(rc))
+ clipTrimTrailingNul(*(XtPointer *)pValReturn, pcLenReturn, fmtX11);
+
+ RTMemFree(pv);
+ }
+ else if ( (fmtX11 == SHCLX11FMT_BMP)
+ && (pCtx->vboxFormats & VBOX_SHCL_FMT_BITMAP))
+ {
+ rc = shClX11RequestDataForX11CallbackHelper(pCtx, VBOX_SHCL_FMT_BITMAP, &pv, &cb);
+ if ( RT_SUCCESS(rc)
+ && (fmtX11 == SHCLX11FMT_BMP))
+ {
+ /* Create a full BMP from it. */
+ rc = ShClDibToBmp(pv, cb, (void **)pValReturn,
+ (size_t *)pcLenReturn);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *atomTypeReturn = *atomTarget;
+ *piFormatReturn = 8;
+ }
+
+ RTMemFree(pv);
+ }
+ else if ( (fmtX11 == SHCLX11FMT_HTML)
+ && (pCtx->vboxFormats & VBOX_SHCL_FMT_HTML))
+ {
+ rc = shClX11RequestDataForX11CallbackHelper(pCtx, VBOX_SHCL_FMT_HTML, &pv, &cb);
+ if (RT_SUCCESS(rc))
+ {
+ /**
+ * The common VBox HTML encoding will be UTF-8.
+ * Before sending it to the X11 clipboard we have to convert it to UTF-8 first.
+ *
+ * Strange that we get UTF-16 from the X11 clipboard, but
+ * in same time we send UTF-8 to X11 clipboard and it works.
+ ** @todo r=andy Verify this.
+ */
+ rc = clipConvertHtmlToX11Data(XtDisplay(pCtx->pWidget),
+ (const char*)pv, cb, atomTarget,
+ atomTypeReturn, pValReturn,
+ pcLenReturn, piFormatReturn);
+ if (RT_SUCCESS(rc))
+ clipTrimTrailingNul(*(XtPointer *)pValReturn, pcLenReturn, fmtX11);
+
+ RTMemFree(pv);
+ }
+ }
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ else if (fmtX11 == SHCLX11FMT_URI_LIST)
+ {
+ if (pCtx->vboxFormats & VBOX_SHCL_FMT_URI_LIST)
+ {
+ rc = shClX11RequestDataForX11CallbackHelper(pCtx, VBOX_SHCL_FMT_URI_LIST, &pv, &cb);
+ if (RT_SUCCESS(rc))
+ {
+ void *pvDst = (void *)XtMalloc(cb);
+ if (pvDst)
+ {
+ memcpy(pvDst, pv, cb);
+
+ *atomTypeReturn = *atomTarget;
+ *pValReturn = (XtPointer)pvDst;
+ *pcLenReturn = cb;
+ *piFormatReturn = 8;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ /* else not supported yet. */
+ }
+#endif
+ else
+ {
+ *atomTypeReturn = XT_CONVERT_FAIL;
+ *pValReturn = (XtPointer)NULL;
+ *pcLenReturn = 0;
+ *piFormatReturn = 0;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ char *pszFmts2 = ShClFormatsToStrA(pCtx->vboxFormats);
+ char *pszAtomName = XGetAtomName(XtDisplay(pCtx->pWidget), *atomTarget);
+
+ LogRel(("Shared Clipboard: Converting VBox formats '%s' to '%s' for X11 (idxFmtX11=%u, fmtX11=%u, atomTarget='%s') failed, rc=%Rrc\n",
+ pszFmts2 ? pszFmts2 : "unknown", g_aFormats[idxFmtX11].pcszAtom, idxFmtX11, fmtX11, pszAtomName ? pszAtomName : "unknown", rc));
+
+ if (pszFmts2)
+ RTStrFree(pszFmts2);
+ if (pszAtomName)
+ XFree(pszAtomName);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns VBox's clipboard data for an X11 client.
+ *
+ * @note Callback for XtOwnSelection.
+ */
+static Boolean clipXtConvertSelectionProc(Widget widget, Atom *atomSelection,
+ Atom *atomTarget,
+ Atom *atomTypeReturn,
+ XtPointer *pValReturn,
+ unsigned long *pcLenReturn,
+ int *piFormatReturn)
+{
+ LogFlowFuncEnter();
+
+ PSHCLX11CTX pCtx = clipLookupContext(widget);
+ if (!pCtx)
+ return False;
+
+ /* Is this the rigt selection (clipboard) we were asked for? */
+ if (!clipIsSupportedSelectionType(pCtx, *atomSelection))
+ return False;
+
+ int rc;
+ if (*atomTarget == clipGetAtom(pCtx, "TARGETS"))
+ rc = clipCreateX11Targets(pCtx, atomTypeReturn, pValReturn,
+ pcLenReturn, piFormatReturn);
+ else
+ rc = clipConvertToX11Data(pCtx, atomTarget, atomTypeReturn,
+ pValReturn, pcLenReturn, piFormatReturn);
+
+#if 0 /** @todo Disabled -- crashes when running with tstClipboardGH-X11. */
+ XSelectionRequestEvent* pReq =
+ XtGetSelectionRequest(widget, *atomSelection, (XtRequestId)NULL);
+ LogFlowFunc(("returning pVBoxWnd=%#x, ownerWnd=%#x, reqWnd=%#x, %RTbool, rc=%Rrc\n",
+ XtWindow(pCtx->pWidget), pReq->owner, pReq->requestor, RT_SUCCESS(rc), rc));
+#endif
+ return RT_SUCCESS(rc) ? True : False;
+}
+
+static void clipXtConvertSelectionProcLose(Widget widget, Atom *atomSelection)
+{
+ RT_NOREF(widget, atomSelection);
+ LogFlowFuncEnter();
+}
+
+static void clipXtConvertSelectionProcDone(Widget widget, Atom *atomSelection, Atom *atomTarget)
+{
+ RT_NOREF(widget, atomSelection, atomTarget);
+ LogFlowFuncEnter();
+}
+
+/**
+ * Structure used to pass information about formats that VBox supports.
+ */
+typedef struct _CLIPNEWVBOXFORMATS
+{
+ /** Context information for the X11 clipboard. */
+ PSHCLX11CTX pCtx;
+ /** Formats supported by VBox. */
+ SHCLFORMATS Formats;
+} CLIPNEWVBOXFORMATS, *PCLIPNEWVBOXFORMATS;
+
+
+
+/**
+ * Invalidates the local cache of the data in the VBox clipboard.
+ *
+ * @param pCtx The X11 clipboard context to use.
+ */
+static void clipInvalidateClipboardCache(PSHCLX11CTX pCtx)
+{
+ if (pCtx->pvUnicodeCache != NULL)
+ {
+ RTMemFree(pCtx->pvUnicodeCache);
+ pCtx->pvUnicodeCache = NULL;
+ }
+}
+
+/**
+ * Takes possession of the X11 clipboard (and middle-button selection).
+ *
+ * @param pCtx The X11 clipboard context to use.
+ * @param uFormats Clipboard formats to set.
+ */
+static void clipGrabX11Clipboard(PSHCLX11CTX pCtx, SHCLFORMATS uFormats)
+{
+ LogFlowFuncEnter();
+
+ /** @ŧodo r=andy The docs say: "the value CurrentTime is not acceptable" here!? */
+ if (XtOwnSelection(pCtx->pWidget, clipGetAtom(pCtx, "CLIPBOARD"),
+ CurrentTime,
+ clipXtConvertSelectionProc, clipXtConvertSelectionProcLose, clipXtConvertSelectionProcDone))
+ {
+ pCtx->vboxFormats = uFormats;
+
+ /* Grab the middle-button paste selection too. */
+ XtOwnSelection(pCtx->pWidget, clipGetAtom(pCtx, "PRIMARY"),
+ CurrentTime, clipXtConvertSelectionProc, NULL, 0);
+#ifndef TESTCASE
+ /* Xt suppresses these if we already own the clipboard, so send them
+ * ourselves. */
+ XSetSelectionOwner(XtDisplay(pCtx->pWidget),
+ clipGetAtom(pCtx, "CLIPBOARD"),
+ XtWindow(pCtx->pWidget), CurrentTime);
+ XSetSelectionOwner(XtDisplay(pCtx->pWidget),
+ clipGetAtom(pCtx, "PRIMARY"),
+ XtWindow(pCtx->pWidget), CurrentTime);
+#endif
+ }
+}
+
+/**
+ * Worker function for ShClX11ReportFormatsToX11 which runs on the
+ * event thread.
+ *
+ * @param pvUserData Pointer to a CLIPNEWVBOXFORMATS structure containing
+ * information about the VBox formats available and the
+ * clipboard context data. Must be freed by the worker.
+ */
+static void ShClX11ReportFormatsToX11Worker(void *pvUserData, void * /* interval */)
+{
+ AssertPtrReturnVoid(pvUserData);
+
+ CLIPNEWVBOXFORMATS *pFormats = (CLIPNEWVBOXFORMATS *)pvUserData;
+
+ PSHCLX11CTX pCtx = pFormats->pCtx;
+ SHCLFORMATS fFormats = pFormats->Formats;
+
+ RTMemFree(pFormats);
+
+#ifdef LOG_ENABLED
+ char *pszFmts = ShClFormatsToStrA(fFormats);
+ AssertPtrReturnVoid(pszFmts);
+ LogRel2(("Shared Clipboard: Reported available VBox formats %s to X11\n", pszFmts));
+ RTStrFree(pszFmts);
+#endif
+
+ clipInvalidateClipboardCache(pCtx);
+ clipGrabX11Clipboard(pCtx, fFormats);
+ clipResetX11Formats(pCtx);
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * Announces new clipboard formats to the X11 clipboard.
+ *
+ * @returns VBox status code.
+ * @param pCtx Context data for the clipboard backend.
+ * @param uFormats Clipboard formats offered.
+ */
+int ShClX11ReportFormatsToX11(PSHCLX11CTX pCtx, SHCLFORMATS uFormats)
+{
+ /*
+ * Immediately return if we are not connected to the X server.
+ */
+ if (!pCtx->fHaveX11)
+ return VINF_SUCCESS;
+
+ int rc;
+
+ /* This must be free'd by the worker callback. */
+ PCLIPNEWVBOXFORMATS pFormats = (PCLIPNEWVBOXFORMATS)RTMemAlloc(sizeof(CLIPNEWVBOXFORMATS));
+ if (pFormats)
+ {
+ pFormats->pCtx = pCtx;
+ pFormats->Formats = uFormats;
+ rc = clipThreadScheduleCall(pCtx, ShClX11ReportFormatsToX11Worker,
+ (XtPointer)pFormats);
+ if (RT_FAILURE(rc))
+ RTMemFree(pFormats);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Converts the data obtained from the X11 clipboard to the required format,
+ * place it in the buffer supplied and signal that data has arrived.
+ *
+ * Converts the text obtained UTF-16LE with Windows EOLs.
+ * Converts full BMP data to DIB format.
+ */
+SHCL_X11_DECL(void) clipConvertDataFromX11Worker(void *pClient, void *pvSrc, unsigned cbSrc)
+{
+ CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *)pClient;
+ AssertPtrReturnVoid(pReq);
+
+ LogFlowFunc(("pReq->uFmtVBox=%#x, pReq->idxFmtX11=%u, pReq->pCtx=%p\n", pReq->uFmtVBox, pReq->idxFmtX11, pReq->pCtx));
+
+ LogRel2(("Shared Clipboard: Converting X11 format '%s' to VBox format %#x\n",
+ g_aFormats[pReq->idxFmtX11].pcszAtom, pReq->uFmtVBox));
+
+ AssertPtr(pReq->pCtx);
+ Assert(pReq->uFmtVBox != VBOX_SHCL_FMT_NONE); /* Sanity. */
+
+ int rc = VINF_SUCCESS;
+
+ void *pvDst = NULL;
+ size_t cbDst = 0;
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY
+ PSHCLX11CTX pCtx = pReq->pCtx;
+ AssertPtr(pReq->pCtx);
+ clipSetXtBusy(pCtx, false);
+ if (clipGetXtNeedsUpdate(pCtx))
+ clipQueryX11Targets(pCtx);
+#endif
+
+ /* If X11 clipboard buffer has no data, libXt can pass to XtGetSelectionValue()
+ * callback an empty string, in this case cbSrc is 0. */
+ if (pvSrc == NULL || cbSrc == 0)
+ {
+ /* The clipboard selection may have changed before we could get it. */
+ rc = VERR_NO_DATA;
+ }
+ else if (pReq->uFmtVBox == VBOX_SHCL_FMT_UNICODETEXT)
+ {
+ /* In which format is the clipboard data? */
+ switch (clipRealFormatForX11Format(pReq->idxFmtX11))
+ {
+ case SHCLX11FMT_UTF8:
+ RT_FALL_THROUGH();
+ case SHCLX11FMT_TEXT:
+ {
+ size_t cwDst;
+ /* If we are given broken UTF-8, we treat it as Latin1. */ /** @todo BUGBUG Is this acceptable? */
+ if (RT_SUCCESS(RTStrValidateEncodingEx((char *)pvSrc, cbSrc, 0)))
+ rc = ShClConvUtf8LFToUtf16CRLF((const char *)pvSrc, cbSrc,
+ (PRTUTF16 *)&pvDst, &cwDst);
+ else
+ rc = ShClConvLatin1LFToUtf16CRLF((char *)pvSrc, cbSrc,
+ (PRTUTF16 *)&pvDst, &cwDst);
+ if (RT_SUCCESS(rc))
+ {
+ cwDst += 1 /* Include terminator */;
+ cbDst = cwDst * sizeof(RTUTF16); /* Convert RTUTF16 units to bytes. */
+ }
+ break;
+ }
+
+ default:
+ {
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+ }
+ }
+ else if (pReq->uFmtVBox == VBOX_SHCL_FMT_BITMAP)
+ {
+ /* In which format is the clipboard data? */
+ switch (clipRealFormatForX11Format(pReq->idxFmtX11))
+ {
+ case SHCLX11FMT_BMP:
+ {
+ const void *pDib;
+ size_t cbDibSize;
+ rc = ShClBmpGetDib((const void *)pvSrc, cbSrc,
+ &pDib, &cbDibSize);
+ if (RT_SUCCESS(rc))
+ {
+ pvDst = RTMemAlloc(cbDibSize);
+ if (!pvDst)
+ rc = VERR_NO_MEMORY;
+ else
+ {
+ memcpy(pvDst, pDib, cbDibSize);
+ cbDst = cbDibSize;
+ }
+ }
+ break;
+ }
+
+ default:
+ {
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+ }
+ }
+ else if (pReq->uFmtVBox == VBOX_SHCL_FMT_HTML)
+ {
+ /* In which format is the clipboard data? */
+ switch (clipRealFormatForX11Format(pReq->idxFmtX11))
+ {
+ case SHCLX11FMT_HTML:
+ {
+ /*
+ * The common VBox HTML encoding will be - UTF-8
+ * because it more general for HTML formats then UTF-16
+ * X11 clipboard returns UTF-16, so before sending it we should
+ * convert it to UTF-8.
+ */
+ pvDst = NULL;
+ cbDst = 0;
+
+ /*
+ * Some applications sends data in UTF-16, some in UTF-8,
+ * without indication it in MIME.
+ *
+ * In case of UTF-16, at least [Open|Libre] Office adds an byte order mark (0xfeff)
+ * at the start of the clipboard data.
+ */
+ if ( cbSrc >= sizeof(RTUTF16)
+ && *(PRTUTF16)pvSrc == VBOX_SHCL_UTF16LEMARKER)
+ {
+ rc = ShClConvUtf16ToUtf8HTML((PRTUTF16)pvSrc, cbSrc / sizeof(RTUTF16), (char**)&pvDst, &cbDst);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("UTF-16 Unicode source (%u bytes):\n%ls\n\n", cbSrc, pvSrc));
+ LogFlowFunc(("Byte Order Mark = %hx", ((PRTUTF16)pvSrc)[0]));
+ LogFlowFunc(("UTF-8 Unicode dest (%u bytes):\n%s\n\n", cbDst, pvDst));
+ }
+ else
+ LogRel(("Shared Clipboard: Converting UTF-16 Unicode failed with %Rrc\n", rc));
+ }
+ else /* Raw data. */
+ {
+ pvDst = RTMemAllocZ(cbSrc + 1 /* '\0' */);
+ if(pvDst)
+ {
+ memcpy(pvDst, pvSrc, cbSrc);
+ cbDst = cbSrc + 1 /* '\0' */;
+ }
+ else
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ default:
+ {
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+ }
+ }
+# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ else if (pReq->uFmtVBox == VBOX_SHCL_FMT_URI_LIST)
+ {
+ /* In which format is the clipboard data? */
+ switch (clipRealFormatForX11Format(pReq->idxFmtX11))
+ {
+ case SHCLX11FMT_URI_LIST:
+ {
+ /* For URI lists we only accept valid UTF-8 encodings. */
+ if (RT_SUCCESS(RTStrValidateEncodingEx((char *)pvSrc, cbSrc, 0)))
+ {
+ /* URI lists on X are strings separated with "\r\n". */
+ RTCList<RTCString> lstRootEntries = RTCString((char *)pvSrc, cbSrc).split("\r\n");
+ for (size_t i = 0; i < lstRootEntries.size(); ++i)
+ {
+ char *pszEntry = RTUriFilePath(lstRootEntries.at(i).c_str());
+ AssertPtrBreakStmt(pszEntry, VERR_INVALID_PARAMETER);
+
+ rc = RTStrAAppend((char **)&pvDst, "http://localhost");
+ AssertRCBreakStmt(rc, VERR_NO_MEMORY);
+ cbDst += (uint32_t)strlen(pszEntry);
+
+
+
+ /** @todo BUGBUG Fix port! */
+ /** @todo Add port + UUID (virtual path). */
+
+ rc = RTStrAAppend((char **)&pvDst, pszEntry);
+ AssertRCBreakStmt(rc, VERR_NO_MEMORY);
+ cbDst += (uint32_t)strlen(pszEntry);
+
+ LogFlowFunc(("URI list entry '%s'\n", (char *)pvDst));
+
+ rc = RTStrAAppend((char **)&pvDst, "\r\n");
+ AssertRCBreakStmt(rc, VERR_NO_MEMORY);
+ cbDst += (uint32_t)strlen("\r\n");
+
+ RTStrFree(pszEntry);
+ }
+
+ if (cbDst)
+ cbDst++; /* Include final (zero) termination. */
+
+ LogFlowFunc(("URI list: cbDst=%RU32\n", cbDst));
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ default:
+ {
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+ }
+ }
+# endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: Converting X11 format '%s' (idxFmtX11=%u) to VBox format %#x failed, rc=%Rrc\n",
+ g_aFormats[pReq->idxFmtX11].pcszAtom, pReq->idxFmtX11, pReq->uFmtVBox, rc));
+
+ SHCLX11READDATAREQ SendData;
+ RT_ZERO(SendData);
+ SendData.pReq = pReq->pReq;
+ SendData.rcCompletion = rc;
+
+ pCtx->Callbacks.pfnOnSendDataToDest(pReq->pCtx->pFrontend, pvDst, cbDst, &SendData);
+
+ RTMemFree(pvDst);
+ RTMemFree(pReq);
+
+ LogFlowFuncLeaveRC(rc);
+}
+
+/**
+ * Converts the data obtained from the X11 clipboard to the required format,
+ * place it in the buffer supplied and signal that data has arrived.
+ *
+ * Converts the text obtained UTF-16LE with Windows EOLs.
+ * Converts full BMP data to DIB format.
+ */
+SHCL_X11_DECL(void) clipConvertDataFromX11(Widget widget, XtPointer pClient,
+ Atom * /* selection */, Atom *atomType,
+ XtPointer pvSrc, long unsigned int *pcLen,
+ int *piFormat)
+{
+ RT_NOREF(widget);
+ if (*atomType == XT_CONVERT_FAIL) /* Xt timeout */
+ clipConvertDataFromX11Worker(pClient, NULL, 0);
+ else
+ {
+ CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *)pClient;
+ if (pReq->pCtx->Callbacks.pfnOnClipboardRead)
+ {
+ void *pvData = NULL;
+ size_t cbData = 0;
+ int rc = pReq->pCtx->Callbacks.pfnOnClipboardRead(pReq->pCtx->pFrontend, pReq->uFmtVBox, &pvData, &cbData, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Feed to conversion worker. */
+ clipConvertDataFromX11Worker(pClient, pvData, cbData);
+ RTMemFree(pvData);
+ }
+ else
+ clipConvertDataFromX11Worker(pClient, NULL, 0);
+ }
+ else /* Call with current data provided by X (default). */
+ clipConvertDataFromX11Worker(pClient, pvSrc, (*pcLen) * (*piFormat) / 8);
+ }
+
+ XtFree((char *)pvSrc);
+}
+
+static int clipGetSelectionValue(PSHCLX11CTX pCtx, SHCLX11FMTIDX idxFmt,
+ CLIPREADX11CBREQ *pReq)
+{
+#ifndef TESTCASE
+ XtGetSelectionValue(pCtx->pWidget, clipGetAtom(pCtx, "CLIPBOARD"),
+ clipAtomForX11Format(pCtx, idxFmt),
+ clipConvertDataFromX11,
+ reinterpret_cast<XtPointer>(pReq),
+ CurrentTime);
+#else
+ tstClipRequestData(pCtx, idxFmt, (void *)pReq);
+#endif
+
+ return VINF_SUCCESS; /** @todo Return real rc. */
+}
+
+/**
+ * Worker function for ShClX11ReadDataFromX11 which runs on the event thread.
+ *
+ * @param pvUserData Pointer to a CLIPREADX11CBREQ structure containing
+ * information about the clipboard read request.
+ * Must be free'd by the worker.
+ */
+static void ShClX11ReadDataFromX11Worker(void *pvUserData, void * /* interval */)
+{
+ AssertPtrReturnVoid(pvUserData);
+
+ CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *)pvUserData;
+ SHCLX11CTX *pCtx = pReq->pCtx;
+ AssertPtrReturnVoid(pCtx);
+
+ LogFlowFunc(("pReq->uFmtVBox=%#x, idxFmtX11=%#x\n", pReq->uFmtVBox, pReq->idxFmtX11));
+
+ int rc = VERR_NO_DATA; /* VBox thinks we have data and we don't. */
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY
+ const bool fXtBusy = clipGetXtBusy(pCtx);
+ clipSetXtBusy(pCtx, true);
+ if (fXtBusy)
+ {
+ /* If the clipboard is busy just fend off the request. */
+ rc = VERR_TRY_AGAIN;
+ }
+ else
+#endif
+ if (pReq->uFmtVBox & VBOX_SHCL_FMT_UNICODETEXT)
+ {
+ pReq->idxFmtX11 = pCtx->idxFmtText;
+ if (pReq->idxFmtX11 != SHCLX11FMT_INVALID)
+ {
+ /* Send out a request for the data to the current clipboard owner. */
+ rc = clipGetSelectionValue(pCtx, pCtx->idxFmtText, pReq);
+ }
+ }
+ else if (pReq->uFmtVBox & VBOX_SHCL_FMT_BITMAP)
+ {
+ pReq->idxFmtX11 = pCtx->idxFmtBmp;
+ if (pReq->idxFmtX11 != SHCLX11FMT_INVALID)
+ {
+ /* Send out a request for the data to the current clipboard owner. */
+ rc = clipGetSelectionValue(pCtx, pCtx->idxFmtBmp, pReq);
+ }
+ }
+ else if (pReq->uFmtVBox & VBOX_SHCL_FMT_HTML)
+ {
+ pReq->idxFmtX11 = pCtx->idxFmtHTML;
+ if (pReq->idxFmtX11 != SHCLX11FMT_INVALID)
+ {
+ /* Send out a request for the data to the current clipboard owner. */
+ rc = clipGetSelectionValue(pCtx, pCtx->idxFmtHTML, pReq);
+ }
+ }
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ else if (pReq->uFmtVBox & VBOX_SHCL_FMT_URI_LIST)
+ {
+ pReq->idxFmtX11 = pCtx->idxFmtURI;
+ if (pReq->idxFmtX11 != SHCLX11FMT_INVALID)
+ {
+ /* Send out a request for the data to the current clipboard owner. */
+ rc = clipGetSelectionValue(pCtx, pCtx->idxFmtURI, pReq);
+ }
+ }
+#endif
+ else
+ {
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY
+ clipSetXtBusy(pCtx, false);
+#endif
+ rc = VERR_NOT_IMPLEMENTED;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ /* The clipboard callback was never scheduled, so we must signal
+ * that the request processing is finished and clean up ourselves. */
+ SHCLX11READDATAREQ SendData;
+ RT_ZERO(SendData);
+ SendData.pReq = pReq->pReq;
+ SendData.rcCompletion = rc;
+
+ pCtx->Callbacks.pfnOnSendDataToDest(pReq->pCtx->pFrontend, NULL /* pv */ ,0 /* cb */, &SendData);
+ RTMemFree(pReq);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+}
+
+/**
+ * Called when VBox wants to read the X11 clipboard.
+ *
+ * @returns VBox status code.
+ * @retval VERR_NO_DATA if format is supported but no data is available currently.
+ * @retval VERR_NOT_IMPLEMENTED if the format is not implemented.
+ * @param pCtx Context data for the clipboard backend.
+ * @param uFmt The format that the VBox would like to receive the data in.
+ * @param pReq Read callback request to use. Will be free'd in the callback on success.
+ * Otherwise the caller has to free this again on error.
+ *
+ * @note We allocate a request structure which must be freed by the worker.
+ */
+int ShClX11ReadDataFromX11(PSHCLX11CTX pCtx, SHCLFORMAT uFmt, CLIPREADCBREQ *pReq)
+{
+ AssertPtrReturn(pReq, VERR_INVALID_POINTER);
+ /*
+ * Immediately return if we are not connected to the X server.
+ */
+ if (!pCtx->fHaveX11)
+ return VERR_NO_DATA;
+
+ int rc = VINF_SUCCESS;
+
+ CLIPREADX11CBREQ *pX11Req = (CLIPREADX11CBREQ *)RTMemAllocZ(sizeof(CLIPREADX11CBREQ));
+ if (pX11Req)
+ {
+ pX11Req->pCtx = pCtx;
+ pX11Req->uFmtVBox = uFmt;
+ pX11Req->pReq = pReq;
+
+ /* We use this to schedule a worker function on the event thread. */
+ rc = clipThreadScheduleCall(pCtx, ShClX11ReadDataFromX11Worker, (XtPointer)pX11Req);
+ if (RT_FAILURE(rc))
+ RTMemFree(pX11Req);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk b/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk
new file mode 100644
index 00000000..b91f356d
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk
@@ -0,0 +1,76 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the Shared Clipboard Guest/Host testcases.
+#
+
+#
+# Copyright (C) 2011-2022 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)
+ if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris)
+
+ PROGRAMS += tstClipboardGH-X11 tstClipboardGH-X11Smoke
+ TESTING += \
+ $(tstClipboardGH-X11_0_OUTDIR)/tstClipboardGH-X11.run \
+ $(tstClipboardGH-X11_0_OUTDIR)/tstClipboardGH-X11Smoke.run
+
+ tstClipboardGH-X11_TEMPLATE = VBOXR3TSTEXE
+ tstClipboardGH-X11_DEFS = VBOX_WITH_HGCM UNIT_TEST TESTCASE
+ tstClipboardGH-X11_SOURCES = \
+ tstClipboardGH-X11.cpp \
+ ../clipboard-x11.cpp \
+ ../clipboard-common.cpp
+ tstClipboardGH-X11_CXXFLAGS += -Wno-array-bounds
+ tstClipboardGH-X11_LIBS = X11 Xt
+ tstClipboardGH-X11_CLEAN = $(tstClipboardGH-X11_0_OUTDIR)/tstClipboardGH-X11.run
+
+ tstClipboardGH-X11Smoke_TEMPLATE = VBOXR3TSTEXE
+ tstClipboardGH-X11Smoke_DEFS = VBOX_WITH_HGCM SMOKETEST
+ tstClipboardGH-X11Smoke_SOURCES = \
+ tstClipboardGH-X11Smoke.cpp \
+ ../clipboard-x11.cpp \
+ ../clipboard-common.cpp
+ tstClipboardGH-X11Smoke_LIBPATH = $(VBOX_LIBPATH_X11)
+ tstClipboardGH-X11Smoke_LIBS = X11 Xt
+ tstClipboardGH-X11Smoke_CLEAN = $(tstClipboardGH-X11Smoke_0_OUTDIR)/tstClipboardGH-X11Smoke.run
+
+ $$(tstClipboardGH-X11_0_OUTDIR)/tstClipboardGH-X11.run: $$(tstClipboardGH-X11_1_STAGE_TARGET)
+ export VBOX_LOG_DEST=nofile; $(tstClipboardGH-X11_1_STAGE_TARGET) quiet
+ $(QUIET)$(APPEND) -t "$@" "done"
+
+ $$(tstClipboardGH-X11Smoke_0_OUTDIR)/tstClipboardGH-X11Smoke.run: $$(tstClipboardGH-X11Smoke_1_STAGE_TARGET)
+ export VBOX_LOG_DEST=nofile; $(tstClipboardGH-X11Smoke_1_STAGE_TARGET) quiet
+ $(QUIET)$(APPEND) -t "$@" "done"
+
+ if defined(VBOX_WITH_QTGUI)
+ include $(PATH_SUB_CURRENT)/tstClipboardQt/Makefile.kmk
+ endif
+
+
+ endif
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp
new file mode 100644
index 00000000..10dcfa6e
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp
@@ -0,0 +1,941 @@
+/* $Id: tstClipboardGH-X11.cpp $ */
+/** @file
+ * Shared Clipboard guest/host X11 code test cases.
+ */
+
+/*
+ * Copyright (C) 2011-2022 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 <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/GuestHost/SharedClipboard-x11.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+#include <iprt/utf16.h>
+
+#include <poll.h>
+#include <X11/Xatom.h>
+
+
+/*********************************************************************************************************************************
+* Externals *
+*********************************************************************************************************************************/
+extern SHCLX11FMTTABLE g_aFormats[];
+
+extern void clipUpdateX11Targets(PSHCLX11CTX pCtx, SHCLX11FMTIDX *pTargets, size_t cTargets);
+extern void clipReportEmpty(PSHCLX11CTX pCtx);
+extern void clipConvertDataFromX11Worker(void *pClient, void *pvSrc, unsigned cbSrc);
+extern SHCLX11FMTIDX clipGetTextFormatFromTargets(PSHCLX11CTX pCtx, SHCLX11FMTIDX *pTargets, size_t cTargets);
+extern SHCLX11FMT clipRealFormatForX11Format(SHCLX11FMTIDX uFmtIdx);
+extern Atom clipGetAtom(PSHCLX11CTX pCtx, const char *pcszName);
+extern void clipQueryX11Targets(PSHCLX11CTX pCtx);
+extern size_t clipReportMaxX11Formats(void);
+
+
+/*********************************************************************************************************************************
+* Internal prototypes *
+*********************************************************************************************************************************/
+static SHCLX11FMTIDX tstClipFindX11FormatByAtomText(const char *pcszAtom);
+
+
+/*********************************************************************************************************************************
+* Prototypes, used for testcases by clipboard-x11.cpp *
+*********************************************************************************************************************************/
+void tstRequestTargets(SHCLX11CTX* pCtx);
+void tstClipRequestData(PSHCLX11CTX pCtx, SHCLX11FMTIDX target, void *closure);
+void tstThreadScheduleCall(void (*proc)(void *, void *), void *client_data);
+
+
+/*********************************************************************************************************************************
+* Own callback implementations *
+*********************************************************************************************************************************/
+extern DECLCALLBACK(void) clipQueryX11TargetsCallback(Widget widget, XtPointer pClient,
+ Atom * /* selection */, Atom *atomType,
+ XtPointer pValue, long unsigned int *pcLen,
+ int *piFormat);
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+#define TESTCASE_WIDGET_ID (Widget)0xffff
+
+
+/* For the purpose of the test case, we just execute the procedure to be
+ * scheduled, as we are running single threaded. */
+void tstThreadScheduleCall(void (*proc)(void *, void *), void *client_data)
+{
+ proc(client_data, NULL);
+}
+
+/* The data in the simulated VBox clipboard. */
+static int g_tst_rcDataVBox = VINF_SUCCESS;
+static void *g_tst_pvDataVBox = NULL;
+static uint32_t g_tst_cbDataVBox = 0;
+
+/* Set empty data in the simulated VBox clipboard. */
+static void tstClipEmptyVBox(PSHCLX11CTX pCtx, int retval)
+{
+ g_tst_rcDataVBox = retval;
+ RTMemFree(g_tst_pvDataVBox);
+ g_tst_pvDataVBox = NULL;
+ g_tst_cbDataVBox = 0;
+ ShClX11ReportFormatsToX11(pCtx, 0);
+}
+
+/* Set the data in the simulated VBox clipboard. */
+static int tstClipSetVBoxUtf16(PSHCLX11CTX pCtx, int retval,
+ const char *pcszData, size_t cb)
+{
+ PRTUTF16 pwszData = NULL;
+ size_t cwData = 0;
+ int rc = RTStrToUtf16Ex(pcszData, RTSTR_MAX, &pwszData, 0, &cwData);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertReturn(cb <= cwData * 2 + 2, VERR_BUFFER_OVERFLOW);
+ void *pv = RTMemDup(pwszData, cb);
+ RTUtf16Free(pwszData);
+ if (pv == NULL)
+ return VERR_NO_MEMORY;
+ if (g_tst_pvDataVBox)
+ RTMemFree(g_tst_pvDataVBox);
+ g_tst_rcDataVBox = retval;
+ g_tst_pvDataVBox = pv;
+ g_tst_cbDataVBox = cb;
+ ShClX11ReportFormatsToX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT);
+ return VINF_SUCCESS;
+}
+
+Display *XtDisplay(Widget w) { NOREF(w); return (Display *) 0xffff; }
+
+void XtAppSetExitFlag(XtAppContext app_context) { NOREF(app_context); }
+
+void XtDestroyWidget(Widget w) { NOREF(w); }
+
+XtAppContext XtCreateApplicationContext(void) { return (XtAppContext)0xffff; }
+
+void XtDestroyApplicationContext(XtAppContext app_context) { NOREF(app_context); }
+
+void XtToolkitInitialize(void) {}
+
+Boolean XtToolkitThreadInitialize(void) { return True; }
+
+Display *XtOpenDisplay(XtAppContext app_context,
+ _Xconst _XtString display_string,
+ _Xconst _XtString application_name,
+ _Xconst _XtString application_class,
+ XrmOptionDescRec *options, Cardinal num_options,
+ int *argc, char **argv)
+{
+ RT_NOREF8(app_context, display_string, application_name, application_class, options, num_options, argc, argv);
+ return (Display *)0xffff;
+}
+
+Widget XtVaAppCreateShell(_Xconst _XtString application_name, _Xconst _XtString application_class,
+ WidgetClass widget_class, Display *display, ...)
+{
+ RT_NOREF(application_name, application_class, widget_class, display);
+ return TESTCASE_WIDGET_ID;
+}
+
+void XtSetMappedWhenManaged(Widget widget, _XtBoolean mapped_when_managed) { RT_NOREF(widget, mapped_when_managed); }
+
+void XtRealizeWidget(Widget widget) { NOREF(widget); }
+
+XtInputId XtAppAddInput(XtAppContext app_context, int source, XtPointer condition, XtInputCallbackProc proc, XtPointer closure)
+{
+ RT_NOREF(app_context, source, condition, proc, closure);
+ return 0xffff;
+}
+
+/* Atoms we need other than the formats we support. */
+static const char *g_tst_apszSupAtoms[] =
+{
+ "PRIMARY", "CLIPBOARD", "TARGETS", "MULTIPLE", "TIMESTAMP"
+};
+
+/* This just looks for the atom names in a couple of tables and returns an
+ * index with an offset added. */
+Atom XInternAtom(Display *, const char *pcsz, int)
+{
+ Atom atom = 0;
+ size_t const cFormats = clipReportMaxX11Formats();
+ size_t i;
+ for (i = 0; i < cFormats; ++i)
+ {
+ if (!strcmp(pcsz, g_aFormats[i].pcszAtom))
+ atom = (Atom) (i + 0x1000);
+ }
+ for (i = 0; i < RT_ELEMENTS(g_tst_apszSupAtoms); ++i)
+ if (!strcmp(pcsz, g_tst_apszSupAtoms[i]))
+ atom = (Atom) (i + 0x2000);
+ Assert(atom); /* Have we missed any atoms? */
+ return atom;
+}
+
+/* Take a request for the targets we are currently offering. */
+static SHCLX11FMTIDX g_tst_aSelTargetsIdx[10] = { 0 };
+static size_t g_tst_cTargets = 0;
+
+void tstRequestTargets(SHCLX11CTX* pCtx)
+{
+ clipUpdateX11Targets(pCtx, g_tst_aSelTargetsIdx, g_tst_cTargets);
+}
+
+/* The current values of the X selection, which will be returned to the
+ * XtGetSelectionValue callback. */
+static Atom g_tst_atmSelType = 0;
+static const void *g_tst_pSelData = NULL;
+static unsigned long g_tst_cSelData = 0;
+static int g_tst_selFormat = 0;
+
+void tstClipRequestData(PSHCLX11CTX pCtx, SHCLX11FMTIDX target, void *closure)
+{
+ RT_NOREF(pCtx);
+ unsigned long count = 0;
+ int format = 0;
+ if (target != g_tst_aSelTargetsIdx[0])
+ {
+ clipConvertDataFromX11Worker(closure, NULL, 0); /* Could not convert to target. */
+ return;
+ }
+ void *pValue = NULL;
+ pValue = g_tst_pSelData ? RTMemDup(g_tst_pSelData, g_tst_cSelData) : NULL;
+ count = g_tst_pSelData ? g_tst_cSelData : 0;
+ format = g_tst_selFormat;
+ if (!pValue)
+ {
+ count = 0;
+ format = 0;
+ }
+ clipConvertDataFromX11Worker(closure, pValue, count * format / 8);
+ if (pValue)
+ RTMemFree(pValue);
+}
+
+/* The formats currently on offer from X11 via the shared clipboard. */
+static uint32_t g_tst_uX11Formats = 0;
+
+static uint32_t tstClipQueryFormats(void)
+{
+ return g_tst_uX11Formats;
+}
+
+static void tstClipInvalidateFormats(void)
+{
+ g_tst_uX11Formats = ~0;
+}
+
+/* Does our clipboard code currently own the selection? */
+static bool g_tst_fOwnsSel = false;
+/* The procedure that is called when we should convert the selection to a
+ * given format. */
+static XtConvertSelectionProc g_tst_pfnSelConvert = NULL;
+/* The procedure which is called when we lose the selection. */
+static XtLoseSelectionProc g_tst_pfnSelLose = NULL;
+/* The procedure which is called when the selection transfer has completed. */
+static XtSelectionDoneProc g_tst_pfnSelDone = NULL;
+
+Boolean XtOwnSelection(Widget widget, Atom selection, Time time,
+ XtConvertSelectionProc convert,
+ XtLoseSelectionProc lose,
+ XtSelectionDoneProc done)
+{
+ RT_NOREF(widget, time);
+ if (selection != XInternAtom(NULL, "CLIPBOARD", 0))
+ return True; /* We don't really care about this. */
+ g_tst_fOwnsSel = true; /* Always succeed. */
+ g_tst_pfnSelConvert = convert;
+ g_tst_pfnSelLose = lose;
+ g_tst_pfnSelDone = done;
+ return True;
+}
+
+void XtDisownSelection(Widget widget, Atom selection, Time time)
+{
+ RT_NOREF(widget, time, selection);
+ g_tst_fOwnsSel = false;
+ g_tst_pfnSelConvert = NULL;
+ g_tst_pfnSelLose = NULL;
+ g_tst_pfnSelDone = NULL;
+}
+
+/* Request the shared clipboard to convert its data to a given format. */
+static bool tstClipConvertSelection(const char *pcszTarget, Atom *type,
+ XtPointer *value, unsigned long *length,
+ int *format)
+{
+ Atom target = XInternAtom(NULL, pcszTarget, 0);
+ if (target == 0)
+ return false;
+ /* Initialise all return values in case we make a quick exit. */
+ *type = XA_STRING;
+ *value = NULL;
+ *length = 0;
+ *format = 0;
+ if (!g_tst_fOwnsSel)
+ return false;
+ if (!g_tst_pfnSelConvert)
+ return false;
+ Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0);
+ if (!g_tst_pfnSelConvert(TESTCASE_WIDGET_ID, &clipAtom, &target, type,
+ value, length, format))
+ return false;
+ if (g_tst_pfnSelDone)
+ g_tst_pfnSelDone(TESTCASE_WIDGET_ID, &clipAtom, &target);
+ return true;
+}
+
+/* Set the current X selection data */
+static void tstClipSetSelectionValues(const char *pcszTarget, Atom type,
+ const void *data,
+ unsigned long count, int format)
+{
+ Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0);
+ g_tst_aSelTargetsIdx[0] = tstClipFindX11FormatByAtomText(pcszTarget);
+ g_tst_cTargets = 1;
+ g_tst_atmSelType = type;
+ g_tst_pSelData = data;
+ g_tst_cSelData = count;
+ g_tst_selFormat = format;
+ if (g_tst_pfnSelLose)
+ g_tst_pfnSelLose(TESTCASE_WIDGET_ID, &clipAtom);
+ g_tst_fOwnsSel = false;
+}
+
+static void tstClipSendTargetUpdate(PSHCLX11CTX pCtx)
+{
+ clipQueryX11Targets(pCtx);
+}
+
+/* Configure if and how the X11 TARGETS clipboard target will fail. */
+static void tstClipSetTargetsFailure(void)
+{
+ g_tst_cTargets = 0;
+}
+
+char *XtMalloc(Cardinal size)
+{
+ return (char *) RTMemAlloc(size);
+}
+
+void XtFree(char *ptr)
+{
+ RTMemFree((void *)ptr);
+}
+
+char *XGetAtomName(Display *display, Atom atom)
+{
+ RT_NOREF(display);
+ const char *pcszName = NULL;
+ if (atom < 0x1000)
+ return NULL;
+ if (0x1000 <= atom && atom < 0x2000)
+ {
+ unsigned index = atom - 0x1000;
+ AssertReturn(index < clipReportMaxX11Formats(), NULL);
+ pcszName = g_aFormats[index].pcszAtom;
+ }
+ else
+ {
+ unsigned index = atom - 0x2000;
+ AssertReturn(index < RT_ELEMENTS(g_tst_apszSupAtoms), NULL);
+ pcszName = g_tst_apszSupAtoms[index];
+ }
+ return (char *)RTMemDup(pcszName, sizeof(pcszName) + 1);
+}
+
+int XFree(void *data)
+{
+ RTMemFree(data);
+ return 0;
+}
+
+void XFreeStringList(char **list)
+{
+ if (list)
+ RTMemFree(*list);
+ RTMemFree(list);
+}
+
+#define TESTCASE_MAX_BUF_SIZE 256
+
+static int g_tst_rcCompleted = VINF_SUCCESS;
+static int g_tst_cbCompleted = 0;
+static CLIPREADCBREQ *g_tst_pCompletedReq = NULL;
+static char g_tst_abCompletedBuf[TESTCASE_MAX_BUF_SIZE];
+
+static DECLCALLBACK(int) tstShClReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser)
+{
+ RT_NOREF(pCtx, pvUser);
+ g_tst_uX11Formats = fFormats;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstShClOnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
+{
+ RT_NOREF(pCtx, uFmt, pvUser);
+ *pcb = g_tst_cbDataVBox;
+ if (g_tst_pvDataVBox != NULL)
+ {
+ void *pv = RTMemDup(g_tst_pvDataVBox, g_tst_cbDataVBox);
+ *ppv = pv;
+ return pv != NULL ? g_tst_rcDataVBox : VERR_NO_MEMORY;
+ }
+ *ppv = NULL;
+ return g_tst_rcDataVBox;
+}
+
+static DECLCALLBACK(int) tstShClOnSendDataToDestCallback(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser)
+{
+ RT_NOREF(pCtx);
+
+ PSHCLX11READDATAREQ pData = (PSHCLX11READDATAREQ)pvUser;
+
+ if (cb <= TESTCASE_MAX_BUF_SIZE)
+ {
+ g_tst_rcCompleted = pData->rcCompletion;
+ if (cb != 0)
+ memcpy(g_tst_abCompletedBuf, pv, cb);
+ }
+ else
+ g_tst_rcCompleted = VERR_BUFFER_OVERFLOW;
+ g_tst_cbCompleted = cb;
+ g_tst_pCompletedReq = pData->pReq;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Looks up the X11 format matching a given X11 atom text.
+ *
+ * @returns the format on success, NIL_CLIPX11FORMAT on failure
+ * @param pcszAtom Atom text to look up format for.
+ */
+static SHCLX11FMTIDX tstClipFindX11FormatByAtomText(const char *pcszAtom)
+{
+ const size_t j = clipReportMaxX11Formats();
+
+ for (unsigned i = 0; i < j; ++i)
+ {
+ if (!strcmp(g_aFormats[i].pcszAtom, pcszAtom))
+ return i;
+ }
+ return NIL_CLIPX11FORMAT;
+}
+
+static bool tstClipTextFormatConversion(PSHCLX11CTX pCtx)
+{
+ bool fSuccess = true;
+ SHCLX11FMTIDX targets[2];
+ SHCLX11FMTIDX x11Format;
+ targets[0] = tstClipFindX11FormatByAtomText("text/plain");
+ targets[1] = tstClipFindX11FormatByAtomText("image/bmp");
+ x11Format = clipGetTextFormatFromTargets(pCtx, targets, 2);
+ if (clipRealFormatForX11Format(x11Format) != SHCLX11FMT_TEXT)
+ fSuccess = false;
+ targets[0] = tstClipFindX11FormatByAtomText("UTF8_STRING");
+ targets[1] = tstClipFindX11FormatByAtomText("text/plain");
+ x11Format = clipGetTextFormatFromTargets(pCtx, targets, 2);
+ if (clipRealFormatForX11Format(x11Format) != SHCLX11FMT_UTF8)
+ fSuccess = false;
+ return fSuccess;
+}
+
+static void tstClipGetCompletedRequest(int *prc, char ** ppc, uint32_t *pcb, CLIPREADCBREQ **ppReq)
+{
+ *prc = g_tst_rcCompleted;
+ *ppc = g_tst_abCompletedBuf;
+ *pcb = g_tst_cbCompleted;
+ *ppReq = g_tst_pCompletedReq;
+}
+
+static void tstStringFromX11(RTTEST hTest, PSHCLX11CTX pCtx,
+ const char *pcszExp, int rcExp)
+{
+ bool retval = true;
+ tstClipSendTargetUpdate(pCtx);
+ if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT)
+ {
+ RTTestFailed(hTest, "Wrong targets reported: %02X\n", tstClipQueryFormats());
+ }
+ else
+ {
+ char *pc;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+ ShClX11ReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
+ int rc = VINF_SUCCESS;
+ uint32_t cbActual = 0;
+ tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ if (rc != rcExp)
+ RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n",
+ rcExp, rc);
+ else if (pReqRet != pReq)
+ RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n",
+ pReq, pReqRet);
+ else if (RT_FAILURE(rcExp))
+ retval = true;
+ else
+ {
+ RTUTF16 wcExp[TESTCASE_MAX_BUF_SIZE / 2];
+ RTUTF16 *pwcExp = wcExp;
+ size_t cwc = 0;
+ rc = RTStrToUtf16Ex(pcszExp, RTSTR_MAX, &pwcExp,
+ RT_ELEMENTS(wcExp), &cwc);
+ size_t cbExp = cwc * 2 + 2;
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbActual != cbExp)
+ {
+ RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n",
+ RT_MIN(TESTCASE_MAX_BUF_SIZE, cbActual), pc, cbActual,
+ pcszExp, cbExp);
+ }
+ else
+ {
+ if (memcmp(pc, wcExp, cbExp) == 0)
+ retval = true;
+ else
+ RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n",
+ TESTCASE_MAX_BUF_SIZE, pc, pcszExp);
+ }
+ }
+ }
+ }
+ if (!retval)
+ RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
+ pcszExp, rcExp);
+}
+
+static void tstLatin1FromX11(RTTEST hTest, PSHCLX11CTX pCtx,
+ const char *pcszExp, int rcExp)
+{
+ bool retval = false;
+ tstClipSendTargetUpdate(pCtx);
+ if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT)
+ RTTestFailed(hTest, "Wrong targets reported: %02X\n",
+ tstClipQueryFormats());
+ else
+ {
+ char *pc;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+ ShClX11ReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
+ int rc = VINF_SUCCESS;
+ uint32_t cbActual = 0;
+ tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ if (rc != rcExp)
+ RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n",
+ rcExp, rc);
+ else if (pReqRet != pReq)
+ RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n",
+ pReq, pReqRet);
+ else if (RT_FAILURE(rcExp))
+ retval = true;
+ else
+ {
+ RTUTF16 wcExp[TESTCASE_MAX_BUF_SIZE / 2];
+ //RTUTF16 *pwcExp = wcExp; - unused
+ size_t cwc;
+ for (cwc = 0; cwc == 0 || pcszExp[cwc - 1] != '\0'; ++cwc)
+ wcExp[cwc] = pcszExp[cwc];
+ size_t cbExp = cwc * 2;
+ if (cbActual != cbExp)
+ {
+ RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n",
+ RT_MIN(TESTCASE_MAX_BUF_SIZE, cbActual), pc, cbActual,
+ pcszExp, cbExp);
+ }
+ else
+ {
+ if (memcmp(pc, wcExp, cbExp) == 0)
+ retval = true;
+ else
+ RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n",
+ TESTCASE_MAX_BUF_SIZE, pc, pcszExp);
+ }
+ }
+ }
+ if (!retval)
+ RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
+ pcszExp, rcExp);
+}
+
+static void tstStringFromVBox(RTTEST hTest, PSHCLX11CTX pCtx, const char *pcszTarget, Atom typeExp, const char *valueExp)
+{
+ RT_NOREF(pCtx);
+ bool retval = false;
+ Atom type;
+ XtPointer value = NULL;
+ unsigned long length;
+ int format;
+ size_t lenExp = strlen(valueExp);
+ if (tstClipConvertSelection(pcszTarget, &type, &value, &length, &format))
+ {
+ if ( type != typeExp
+ || length != lenExp
+ || format != 8
+ || memcmp((const void *) value, (const void *)valueExp,
+ lenExp))
+ {
+ RTTestFailed(hTest, "Bad data: type %d, (expected %d), length %u, (expected %u), format %d (expected %d), value \"%.*s\" (expected \"%.*s\")\n",
+ type, typeExp, length, lenExp, format, 8,
+ RT_MIN(length, 20), value, RT_MIN(lenExp, 20), valueExp);
+ }
+ else
+ retval = true;
+ }
+ else
+ RTTestFailed(hTest, "Conversion failed\n");
+ XtFree((char *)value);
+ if (!retval)
+ RTTestFailureDetails(hTest, "Conversion to %s, expected \"%s\"\n",
+ pcszTarget, valueExp);
+}
+
+static void tstNoX11(PSHCLX11CTX pCtx, const char *pcszTestCtx)
+{
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq;
+ int rc = ShClX11ReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
+ RTTESTI_CHECK_MSG(rc == VERR_NO_DATA, ("context: %s\n", pcszTestCtx));
+}
+
+static void tstStringFromVBoxFailed(RTTEST hTest, PSHCLX11CTX pCtx, const char *pcszTarget)
+{
+ RT_NOREF(pCtx);
+ Atom type;
+ XtPointer value = NULL;
+ unsigned long length;
+ int format;
+ RTTEST_CHECK_MSG(hTest, !tstClipConvertSelection(pcszTarget, &type, &value,
+ &length, &format),
+ (hTest, "Conversion to target %s, should have failed but didn't, returned type %d, length %u, format %d, value \"%.*s\"\n",
+ pcszTarget, type, length, format, RT_MIN(length, 20),
+ value));
+ XtFree((char *)value);
+}
+
+static void tstNoSelectionOwnership(PSHCLX11CTX pCtx, const char *pcszTestCtx)
+{
+ RT_NOREF(pCtx);
+ RTTESTI_CHECK_MSG(!g_tst_fOwnsSel, ("context: %s\n", pcszTestCtx));
+}
+
+static void tstBadFormatRequestFromHost(RTTEST hTest, PSHCLX11CTX pCtx)
+{
+ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ tstClipSendTargetUpdate(pCtx);
+ if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT)
+ RTTestFailed(hTest, "Wrong targets reported: %02X\n",
+ tstClipQueryFormats());
+ else
+ {
+ char *pc;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+ ShClX11ReadDataFromX11(pCtx, 0xF000 /* vboxFormat */, pReq); /* Bad format. */
+ int rc = VINF_SUCCESS;
+ uint32_t cbActual = 0;
+ tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ if (rc != VERR_NOT_IMPLEMENTED)
+ RTTestFailed(hTest, "Wrong return code, expected VERR_NOT_IMPLEMENTED, got %Rrc\n",
+ rc);
+ tstClipSetSelectionValues("", XA_STRING, "", sizeof(""), 8);
+ tstClipSendTargetUpdate(pCtx);
+ if (tstClipQueryFormats() == VBOX_SHCL_FMT_UNICODETEXT)
+ RTTestFailed(hTest, "Failed to report targets after bad host request.\n");
+ }
+}
+
+int main()
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstClipboardGH-X11", &hTest);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+ RTTestBanner(hTest);
+
+ /*
+ * Run the tests.
+ */
+ SHCLCALLBACKS Callbacks;
+ RT_ZERO(Callbacks);
+ Callbacks.pfnReportFormats = tstShClReportFormatsCallback;
+ Callbacks.pfnOnRequestDataFromSource = tstShClOnRequestDataFromSourceCallback;
+ Callbacks.pfnOnSendDataToDest = tstShClOnSendDataToDestCallback;
+
+ SHCLX11CTX X11Ctx;
+ rc = ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, false /* fHeadless */);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+
+ char *pc;
+ uint32_t cbActual;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+
+ /* UTF-8 from X11 */
+ RTTestSub(hTest, "reading UTF-8 from X11");
+ /* Simple test */
+ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ tstStringFromX11(hTest, &X11Ctx, "hello world", VINF_SUCCESS);
+ /* With an embedded carriage return */
+ tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
+ "hello\nworld", sizeof("hello\nworld"), 8);
+ tstStringFromX11(hTest, &X11Ctx, "hello\r\nworld", VINF_SUCCESS);
+ /* With an embedded CRLF */
+ tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
+ "hello\r\nworld", sizeof("hello\r\nworld"), 8);
+ tstStringFromX11(hTest, &X11Ctx, "hello\r\r\nworld", VINF_SUCCESS);
+ /* With an embedded LFCR */
+ tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
+ "hello\n\rworld", sizeof("hello\n\rworld"), 8);
+ tstStringFromX11(hTest, &X11Ctx, "hello\r\n\rworld", VINF_SUCCESS);
+ /* An empty string */
+ tstClipSetSelectionValues("text/plain;charset=utf-8", XA_STRING, "",
+ sizeof(""), 8);
+ tstStringFromX11(hTest, &X11Ctx, "", VINF_SUCCESS);
+ /* With an embedded UTF-8 character. */
+ tstClipSetSelectionValues("STRING", XA_STRING,
+ "100\xE2\x82\xAC" /* 100 Euro */,
+ sizeof("100\xE2\x82\xAC"), 8);
+ tstStringFromX11(hTest, &X11Ctx, "100\xE2\x82\xAC", VINF_SUCCESS);
+ /* A non-zero-terminated string */
+ tstClipSetSelectionValues("TEXT", XA_STRING,
+ "hello world", sizeof("hello world") - 1, 8);
+ tstStringFromX11(hTest, &X11Ctx, "hello world", VINF_SUCCESS);
+
+ /* Latin1 from X11 */
+ RTTestSub(hTest, "reading Latin1 from X11");
+ /* Simple test */
+ tstClipSetSelectionValues("STRING", XA_STRING, "Georges Dupr\xEA",
+ sizeof("Georges Dupr\xEA"), 8);
+ tstLatin1FromX11(hTest, &X11Ctx, "Georges Dupr\xEA", VINF_SUCCESS);
+ /* With an embedded carriage return */
+ tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\nDupr\xEA",
+ sizeof("Georges\nDupr\xEA"), 8);
+ tstLatin1FromX11(hTest, &X11Ctx, "Georges\r\nDupr\xEA", VINF_SUCCESS);
+ /* With an embedded CRLF */
+ tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\r\nDupr\xEA",
+ sizeof("Georges\r\nDupr\xEA"), 8);
+ tstLatin1FromX11(hTest, &X11Ctx, "Georges\r\r\nDupr\xEA", VINF_SUCCESS);
+ /* With an embedded LFCR */
+ tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\n\rDupr\xEA",
+ sizeof("Georges\n\rDupr\xEA"), 8);
+ tstLatin1FromX11(hTest, &X11Ctx, "Georges\r\n\rDupr\xEA", VINF_SUCCESS);
+ /* A non-zero-terminated string */
+ tstClipSetSelectionValues("text/plain", XA_STRING,
+ "Georges Dupr\xEA!",
+ sizeof("Georges Dupr\xEA!") - 1, 8);
+ tstLatin1FromX11(hTest, &X11Ctx, "Georges Dupr\xEA!", VINF_SUCCESS);
+
+ /*
+ * Unknown X11 format
+ */
+ RTTestSub(hTest, "handling of an unknown X11 format");
+ tstClipInvalidateFormats();
+ tstClipSetSelectionValues("CLIPBOARD", XA_STRING, "Test",
+ sizeof("Test"), 8);
+ tstClipSendTargetUpdate(&X11Ctx);
+ RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0,
+ (hTest, "Failed to send a format update notification\n"));
+
+ /*
+ * Timeout from X11
+ */
+ RTTestSub(hTest, "X11 timeout");
+ tstClipSetSelectionValues("UTF8_STRING", XT_CONVERT_FAIL, NULL,0, 8);
+ tstStringFromX11(hTest, &X11Ctx, "", VERR_NO_DATA);
+
+ /*
+ * No data in X11 clipboard
+ */
+ RTTestSub(hTest, "a data request from an empty X11 clipboard");
+ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, NULL,
+ 0, 8);
+ ShClX11ReadDataFromX11(&X11Ctx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
+ tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ RTTEST_CHECK_MSG(hTest, rc == VERR_NO_DATA,
+ (hTest, "Returned %Rrc instead of VERR_NO_DATA\n",
+ rc));
+ RTTEST_CHECK_MSG(hTest, pReqRet == pReq,
+ (hTest, "Wrong returned request data, expected %p, got %p\n",
+ pReq, pReqRet));
+
+ /*
+ * Ensure that VBox is notified when we return the CB to X11
+ */
+ RTTestSub(hTest, "notification of switch to X11 clipboard");
+ tstClipInvalidateFormats();
+ clipReportEmpty(&X11Ctx);
+ RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0,
+ (hTest, "Failed to send a format update (release) notification\n"));
+
+ /*
+ * Request for an invalid VBox format from X11
+ */
+ RTTestSub(hTest, "a request for an invalid VBox format from X11");
+ /* Testing for 0xffff will go into handling VBOX_SHCL_FMT_UNICODETEXT, where we don't have
+ * have any data at the moment so far, so this will return VERR_NO_DATA. */
+ ShClX11ReadDataFromX11(&X11Ctx, 0xffff /* vboxFormat */, pReq);
+ tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ RTTEST_CHECK_MSG(hTest, rc == VERR_NO_DATA,
+ (hTest, "Returned %Rrc instead of VERR_NO_DATA\n",
+ rc));
+ RTTEST_CHECK_MSG(hTest, pReqRet == pReq,
+ (hTest, "Wrong returned request data, expected %p, got %p\n",
+ pReq, pReqRet));
+
+ /*
+ * Targets failure from X11
+ */
+ RTTestSub(hTest, "X11 targets conversion failure");
+ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ tstClipSetTargetsFailure();
+ Atom atom = XA_STRING;
+ long unsigned int cLen = 0;
+ int format = 8;
+ clipQueryX11TargetsCallback(NULL, (XtPointer) &X11Ctx, NULL, &atom, NULL, &cLen,
+ &format);
+ RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0,
+ (hTest, "Wrong targets reported: %02X\n",
+ tstClipQueryFormats()));
+
+ /*
+ * X11 text format conversion
+ */
+ RTTestSub(hTest, "handling of X11 selection targets");
+ RTTEST_CHECK_MSG(hTest, tstClipTextFormatConversion(&X11Ctx),
+ (hTest, "failed to select the right X11 text formats\n"));
+ /*
+ * UTF-8 from VBox
+ */
+ RTTestSub(hTest, "reading UTF-8 from VBox");
+ /* Simple test */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world",
+ sizeof("hello world") * 2);
+ tstStringFromVBox(hTest, &X11Ctx, "UTF8_STRING",
+ clipGetAtom(&X11Ctx, "UTF8_STRING"), "hello world");
+ /* With an embedded carriage return */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello\r\nworld",
+ sizeof("hello\r\nworld") * 2);
+ tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=UTF-8",
+ clipGetAtom(&X11Ctx, "text/plain;charset=UTF-8"),
+ "hello\nworld");
+ /* With an embedded CRCRLF */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello\r\r\nworld",
+ sizeof("hello\r\r\nworld") * 2);
+ tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=UTF-8",
+ clipGetAtom(&X11Ctx, "text/plain;charset=UTF-8"),
+ "hello\r\nworld");
+ /* With an embedded CRLFCR */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello\r\n\rworld",
+ sizeof("hello\r\n\rworld") * 2);
+ tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=UTF-8",
+ clipGetAtom(&X11Ctx, "text/plain;charset=UTF-8"),
+ "hello\n\rworld");
+ /* An empty string */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "", 2);
+ tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=utf-8",
+ clipGetAtom(&X11Ctx, "text/plain;charset=utf-8"), "");
+ /* With an embedded UTF-8 character. */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "100\xE2\x82\xAC" /* 100 Euro */,
+ 10);
+ tstStringFromVBox(hTest, &X11Ctx, "STRING",
+ clipGetAtom(&X11Ctx, "STRING"), "100\xE2\x82\xAC");
+ /* A non-zero-terminated string */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world",
+ sizeof("hello world") * 2 - 2);
+ tstStringFromVBox(hTest, &X11Ctx, "TEXT", clipGetAtom(&X11Ctx, "TEXT"),
+ "hello world");
+
+ /*
+ * Timeout from VBox
+ */
+ RTTestSub(hTest, "reading from VBox with timeout");
+ tstClipEmptyVBox(&X11Ctx, VERR_TIMEOUT);
+ tstStringFromVBoxFailed(hTest, &X11Ctx, "UTF8_STRING");
+
+ /*
+ * No data in VBox clipboard
+ */
+ RTTestSub(hTest, "an empty VBox clipboard");
+ tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
+ tstClipEmptyVBox(&X11Ctx, VINF_SUCCESS);
+ RTTEST_CHECK_MSG(hTest, g_tst_fOwnsSel,
+ (hTest, "VBox grabbed the clipboard with no data and we ignored it\n"));
+ tstStringFromVBoxFailed(hTest, &X11Ctx, "UTF8_STRING");
+
+ /*
+ * An unknown VBox format
+ */
+ RTTestSub(hTest, "reading an unknown VBox format");
+ tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "", 2);
+ ShClX11ReportFormatsToX11(&X11Ctx, 0xa0000);
+ RTTEST_CHECK_MSG(hTest, g_tst_fOwnsSel,
+ (hTest, "VBox grabbed the clipboard with unknown data and we ignored it\n"));
+ tstStringFromVBoxFailed(hTest, &X11Ctx, "UTF8_STRING");
+
+ /*
+ * VBox requests a bad format
+ */
+ RTTestSub(hTest, "recovery from a bad format request");
+ tstBadFormatRequestFromHost(hTest, &X11Ctx);
+
+ ShClX11Destroy(&X11Ctx);
+
+ /*
+ * Headless clipboard tests
+ */
+ rc = ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, true /* fHeadless */);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+
+ /* Read from X11 */
+ RTTestSub(hTest, "reading from X11, headless clipboard");
+ /* Simple test */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "",
+ sizeof("") * 2);
+ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ tstNoX11(&X11Ctx, "reading from X11, headless clipboard");
+
+ /* Read from VBox */
+ RTTestSub(hTest, "reading from VBox, headless clipboard");
+ /* Simple test */
+ tstClipEmptyVBox(&X11Ctx, VERR_WRONG_ORDER);
+ tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world",
+ sizeof("hello world") * 2);
+ tstNoSelectionOwnership(&X11Ctx, "reading from VBox, headless clipboard");
+
+ ShClX11Destroy(&X11Ctx);
+
+ return RTTestSummaryAndDestroy(hTest);
+}
+
diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp
new file mode 100644
index 00000000..7cdf1692
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp
@@ -0,0 +1,103 @@
+/* $Id: tstClipboardGH-X11Smoke.cpp $ */
+/** @file
+ * Shared Clipboard guest/host X11 code smoke tests.
+ */
+
+/*
+ * Copyright (C) 2011-2022 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
+ */
+
+/* This is a simple test case that just starts a copy of the X11 clipboard
+ * backend, checks the X11 clipboard and exits. If ever needed I will add an
+ * interactive mode in which the user can read and copy to the clipboard from
+ * the command line. */
+
+#include <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/test.h>
+
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/GuestHost/SharedClipboard-x11.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+
+
+static DECLCALLBACK(int) tstShClReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser)
+{
+ RT_NOREF(pCtx, fFormats, pvUser);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstShClOnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
+{
+ RT_NOREF(pCtx, uFmt, ppv, pcb, pvUser);
+ return VERR_NO_DATA;
+}
+
+static DECLCALLBACK(int) tstShClOnSendDataToDest(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser)
+{
+ RT_NOREF(pCtx, pv, cb, pvUser);
+ return VINF_SUCCESS;
+}
+
+int main()
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstClipboardGH-X11Smoke", &hTest);
+ if (rc)
+ return rc;
+ RTTestBanner(hTest);
+
+ /*
+ * Run the test.
+ */
+ rc = VINF_SUCCESS;
+ /* We can't test anything without an X session, so just return success
+ * in that case. */
+ if (!RTEnvExist("DISPLAY"))
+ {
+ RTTestPrintf(hTest, RTTESTLVL_INFO,
+ "X11 not available, not running test\n");
+ return RTTestSummaryAndDestroy(hTest);
+ }
+
+ SHCLCALLBACKS Callbacks;
+ RT_ZERO(Callbacks);
+ Callbacks.pfnReportFormats = tstShClReportFormatsCallback;
+ Callbacks.pfnOnRequestDataFromSource = tstShClOnRequestDataFromSourceCallback;
+ Callbacks.pfnOnSendDataToDest = tstShClOnSendDataToDest;
+
+ SHCLX11CTX X11Ctx;
+ rc = ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, false);
+ AssertRCReturn(rc, 1);
+ rc = ShClX11ThreadStart(&X11Ctx, false /* fGrab */);
+ AssertRCReturn(rc, 1);
+ /* Give the clipboard time to synchronise. */
+ RTThreadSleep(500);
+ rc = ShClX11ThreadStop(&X11Ctx);
+ AssertRCReturn(rc, 1);
+ ShClX11Destroy(&X11Ctx);
+ return RTTestSummaryAndDestroy(hTest);
+}
+
diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/Makefile.kmk b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/Makefile.kmk
new file mode 100644
index 00000000..4fad271d
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/Makefile.kmk
@@ -0,0 +1,52 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for tstClipboardQt.
+#
+
+#
+# Copyright (C) 2011-2022 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
+
+#
+# Targets and units.
+#
+USES += qt5
+PROGRAMS += tstClipboardQt
+
+#
+# tstClipboardQt
+#
+tstClipboardQt_TEMPLATE = VBOXQTGUIEXE
+tstClipboardQt_NAME = tstClipboardQt
+tstClipboardQt_CXXFLAGS = \
+ $(VBOX_GCC_Wno-implicit-fallthrough) \
+ $(VBOX_GCC_Wno-deprecated-declarations)
+
+tstClipboardQt_SOURCES = \
+ tstClipboardQt.cpp
+
+# The Qt modules we're using.
+tstClipboardQt_QT_MODULES = Core Gui Widgets PrintSupport
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/tstClipboardQt.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/tstClipboardQt.cpp
new file mode 100644
index 00000000..a42c9ebf
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/tstClipboardQt.cpp
@@ -0,0 +1,47 @@
+/* $Id: tstClipboardQt.cpp $ */
+/** @file
+ * Shared Clipboard guest/host Qt code test cases.
+ */
+
+/*
+ * Copyright (C) 2011-2022 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 <QApplication>
+#include <QMainWindow>
+#include <QTextEdit>
+#include <QHBoxLayout>
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+ QMainWindow window;
+ QWidget *pWidget = new QWidget;
+ window.setCentralWidget(pWidget);
+ QHBoxLayout *pLayout = new QHBoxLayout(pWidget);
+
+ QTextEdit *pTextEdit = new QTextEdit;
+ pLayout->addWidget(pTextEdit);
+
+
+ window.show();
+
+ app.exec();
+}