summaryrefslogtreecommitdiffstats
path: root/src/VBox/GuestHost/DragAndDrop
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/GuestHost/DragAndDrop
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/GuestHost/DragAndDrop')
-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.kmk68
-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.cpp206
-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
13 files changed, 3606 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..5dafc3bf
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDDroppedFiles.cpp
@@ -0,0 +1,450 @@
+/* $Id: DnDDroppedFiles.cpp $ */
+/** @file
+ * DnD - Directory handling.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_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..cbb4a077
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDMIME.cpp
@@ -0,0 +1,63 @@
+/* $Id: DnDMIME.cpp $ */
+/** @file
+ * DnD - Path list class.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_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..e4ac219e
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDPath.cpp
@@ -0,0 +1,212 @@
+/* $Id: DnDPath.cpp $ */
+/** @file
+ * DnD - Path handling.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_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..de7893b8
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDTransferList.cpp
@@ -0,0 +1,1295 @@
+/* $Id: DnDTransferList.cpp $ */
+/** @file
+ * DnD - transfer list implemenation.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/**
+ * 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..eadaa395
--- /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-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_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..43fba52a
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDUtils.cpp
@@ -0,0 +1,177 @@
+/* $Id: DnDUtils.cpp $ */
+/** @file
+ * DnD - Common utility functions.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* 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..91fffe5a
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/Makefile.kmk
@@ -0,0 +1,68 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the shared DnD code for both, host and guest.
+#
+
+#
+# Copyright (C) 2014-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+ifdef VBOX_WITH_TESTCASES
+ include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk
+endif
+
+VBOX_PATH_HOSTSERVICES_COMMON = $(PATH_ROOT)/src/VBox/HostServices/common
+
+#
+# DnDGuestR3Lib - For tools on the guest side,
+# e.g. VBoxClient/VBoxTray.
+#
+ifdef VBOX_WITH_ADDITIONS
+ LIBRARIES += VBoxDnDGuestR3Lib
+endif
+VBoxDnDGuestR3Lib_TEMPLATE = VBoxGuestR3Lib
+VBoxDnDGuestR3Lib_SOURCES = \
+ DnDDroppedFiles.cpp \
+ DnDMIME.cpp \
+ DnDPath.cpp \
+ DnDTransferObject.cpp \
+ DnDTransferList.cpp \
+ DnDUtils.cpp
+
+# LIBRARIES.win.amd64 += VBoxDnDGuestR3Lib-x86
+# VBoxDnDGuestR3Lib-x86_EXTENDS := VBoxDnDGuestR3Lib
+# VBoxDnDGuestR3Lib-x86_BLD_TRG_ARCH := x86
+
+#
+# DnDHostR3Lib - For the host side, e.g. Main
+# and frontends.
+#
+LIBRARIES += VBoxDnDHostR3Lib
+VBoxDnDHostR3Lib_TEMPLATE = VBoxR3Dll
+VBoxDnDHostR3Lib_SOURCES = \
+ $(VBoxDnDGuestR3Lib_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..05568ce3
--- /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-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK)
+
+ 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..8cface1b
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/testcase/tstDnDPath.cpp
@@ -0,0 +1,100 @@
+/* $Id: tstDnDPath.cpp $ */
+/** @file
+ * DnD path tests.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <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..164b6200
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/testcase/tstDnDTransferList.cpp
@@ -0,0 +1,206 @@
+/* $Id: tstDnDTransferList.cpp $ */
+/** @file
+ * DnD transfer list tests.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <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. */
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ RTStrPrintf(szPathTest, sizeof(szPathTest), "C:/Windows/");
+ static const char s_szURI[] =
+ "file:///C:/Windows/System32/Boot/\r\n"
+ "file:///C:/Windows/System/\r\n";
+ static const char s_szURIFmtURI[] =
+ "file:///base/System32/Boot/\r\n"
+ "file:///base/System/\r\n";
+ static const char s_szURIFmtNative[] =
+ "\\base\\System32\\Boot\\\r\n"
+ "\\base\\System\\\r\n";
+#else
+ RTStrPrintf(szPathTest, sizeof(szPathTest), "/usr/");
+ static const char s_szURI[] =
+ "file:///usr/bin/\r\n"
+ "file:///usr/lib/\r\n";
+ static const char s_szURIFmtURI[] =
+ "file:///base/bin/\r\n"
+ "file:///base/lib/\r\n";
+ static const char s_szURIFmtNative[] =
+ "/base/bin/\r\n"
+ "/base/lib/\r\n";
+#endif
+
+ RTTEST_CHECK_RC(hTest, DnDTransferListAppendPathsFromBuffer(&list, DNDTRANSFERLISTFMT_URI, s_szURI, sizeof(s_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, s_szURIFmtURI) == 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, s_szURIFmtNative) == 0,
+ (hTest, "Expected %.*Rhxs\nGot %.*Rhxs\n '%s'",
+ sizeof(s_szURIFmtNative) - 1, s_szURIFmtNative,
+ strlen(pszBuf), pszBuf, 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..f887aac3
--- /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-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <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..4e3d8795
--- /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-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# 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..0d7a6144
--- /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-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <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();
+}