summaryrefslogtreecommitdiffstats
path: root/src/VBox/GuestHost/DragAndDrop
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
commitf8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch)
tree26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/GuestHost/DragAndDrop
parentInitial commit. (diff)
downloadvirtualbox-upstream.tar.xz
virtualbox-upstream.zip
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
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.cpp264
-rw-r--r--src/VBox/GuestHost/DragAndDrop/DnDMIME.cpp43
-rw-r--r--src/VBox/GuestHost/DragAndDrop/DnDPath.cpp72
-rw-r--r--src/VBox/GuestHost/DragAndDrop/DnDURIList.cpp565
-rw-r--r--src/VBox/GuestHost/DragAndDrop/DnDURIObject.cpp565
-rw-r--r--src/VBox/GuestHost/DragAndDrop/Makefile.kmk59
6 files changed, 1568 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..1b3651cb
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDDroppedFiles.cpp
@@ -0,0 +1,264 @@
+/* $Id: DnDDroppedFiles.cpp $ */
+/** @file
+ * DnD - Directory handling.
+ */
+
+/*
+ * Copyright (C) 2014-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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/path.h>
+#include <iprt/string.h>
+
+
+#include <VBox/log.h>
+
+DnDDroppedFiles::DnDDroppedFiles(void)
+ : m_fOpen(0)
+ , m_hDir(NULL) { }
+
+DnDDroppedFiles::DnDDroppedFiles(const char *pszPath, DNDURIDROPPEDFILEFLAGS fFlags /* = DNDURIDROPPEDFILE_FLAGS_NONE */)
+ : m_fOpen(0)
+ , m_hDir(NULL)
+{
+ OpenEx(pszPath, fFlags);
+}
+
+DnDDroppedFiles::~DnDDroppedFiles(void)
+{
+ /* Only make sure to not leak any handles and stuff, don't delete any
+ * directories / files here. */
+ closeInternal();
+}
+
+int DnDDroppedFiles::AddFile(const char *pszFile)
+{
+ AssertPtrReturn(pszFile, VERR_INVALID_POINTER);
+
+ if (!this->m_lstFiles.contains(pszFile))
+ this->m_lstFiles.append(pszFile);
+ return VINF_SUCCESS;
+}
+
+int DnDDroppedFiles::AddDir(const char *pszDir)
+{
+ AssertPtrReturn(pszDir, VERR_INVALID_POINTER);
+
+ if (!this->m_lstDirs.contains(pszDir))
+ this->m_lstDirs.append(pszDir);
+ return VINF_SUCCESS;
+}
+
+int DnDDroppedFiles::closeInternal(void)
+{
+ int rc;
+ if (this->m_hDir != NULL)
+ {
+ rc = RTDirClose(this->m_hDir);
+ if (RT_SUCCESS(rc))
+ this->m_hDir = NULL;
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+int DnDDroppedFiles::Close(void)
+{
+ return closeInternal();
+}
+
+const char *DnDDroppedFiles::GetDirAbs(void) const
+{
+ return this->m_strPathAbs.c_str();
+}
+
+bool DnDDroppedFiles::IsOpen(void) const
+{
+ return (this->m_hDir != NULL);
+}
+
+int DnDDroppedFiles::OpenEx(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 pszDropDir[RTPATH_MAX];
+ RTStrPrintf(pszDropDir, sizeof(pszDropDir), "%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(pszDropDir, sizeof(pszDropDir), "VirtualBox Dropped Files"); /** @todo Make this tag configurable? */
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Create it when necessary. */
+ if (!RTDirExists(pszDropDir))
+ {
+ rc = RTDirCreateFullPath(pszDropDir, 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 pszTime[64];
+ RTTIMESPEC time;
+ if (!RTTimeSpecToString(RTTimeNow(&time), pszTime, sizeof(pszTime)))
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ rc = DnDPathSanitizeFilename(pszTime, sizeof(pszTime));
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = RTPathAppend(pszDropDir, sizeof(pszDropDir), pszTime);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Create it (only accessible by the current user) */
+ rc = RTDirCreateUniqueNumbered(pszDropDir, sizeof(pszDropDir), RTFS_UNIX_IRWXU, 3, '-');
+ if (RT_SUCCESS(rc))
+ {
+ RTDIR hDir;
+ rc = RTDirOpen(&hDir, pszDropDir);
+ if (RT_SUCCESS(rc))
+ {
+ this->m_hDir = hDir;
+ this->m_strPathAbs = pszDropDir;
+ this->m_fOpen = fFlags;
+ }
+ }
+
+ } while (0);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+int DnDDroppedFiles::OpenTemp(DNDURIDROPPEDFILEFLAGS fFlags /* = DNDURIDROPPEDFILE_FLAGS_NONE */)
+{
+ 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 = OpenEx(szTemp, fFlags);
+
+ return rc;
+}
+
+int DnDDroppedFiles::Reset(bool fRemoveDropDir)
+{
+ int rc = closeInternal();
+ if (RT_SUCCESS(rc))
+ {
+ if (fRemoveDropDir)
+ {
+ rc = Rollback();
+ }
+ else
+ {
+ this->m_lstDirs.clear();
+ this->m_lstFiles.clear();
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+int DnDDroppedFiles::Reopen(void)
+{
+ if (this->m_strPathAbs.isEmpty())
+ return VERR_NOT_FOUND;
+
+ return OpenEx(this->m_strPathAbs.c_str(), this->m_fOpen);
+}
+
+int DnDDroppedFiles::Rollback(void)
+{
+ if (this->m_strPathAbs.isEmpty())
+ 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;
+ for (size_t i = 0; i < this->m_lstFiles.size(); i++)
+ {
+ rc2 = RTFileDelete(this->m_lstFiles.at(i).c_str());
+ if (RT_SUCCESS(rc2))
+ this->m_lstFiles.removeAt(i);
+ else if (RT_SUCCESS(rc))
+ rc = rc2;
+ /* Keep going. */
+ }
+
+ for (size_t i = 0; i < this->m_lstDirs.size(); i++)
+ {
+ rc2 = RTDirRemove(this->m_lstDirs.at(i).c_str());
+ if (RT_SUCCESS(rc2))
+ this->m_lstDirs.removeAt(i);
+ else if (RT_SUCCESS(rc))
+ rc = rc2;
+ /* Keep going. */
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ Assert(this->m_lstFiles.isEmpty());
+ Assert(this->m_lstDirs.isEmpty());
+
+ rc2 = closeInternal();
+ 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(this->m_strPathAbs.c_str());
+ }
+ 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..0162f534
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDMIME.cpp
@@ -0,0 +1,43 @@
+/* $Id: DnDMIME.cpp $ */
+/** @file
+ * DnD - Path list class.
+ */
+
+/*
+ * Copyright (C) 2014-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_GUEST_DND
+#include <VBox/GuestHost/DragAndDrop.h>
+
+#include <iprt/string.h>
+
+
+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);
+}
+
+bool DnDMIMENeedsDropDir(const char *pcszFormat, size_t cchFormatMax)
+{
+ bool fNeedsDropDir = false;
+ if (!RTStrNICmp(pcszFormat, "text/uri-list", cchFormatMax)) /** @todo Add "x-special/gnome-icon-list"? */
+ fNeedsDropDir = true;
+
+ return fNeedsDropDir;
+}
+
diff --git a/src/VBox/GuestHost/DragAndDrop/DnDPath.cpp b/src/VBox/GuestHost/DragAndDrop/DnDPath.cpp
new file mode 100644
index 00000000..3755e039
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDPath.cpp
@@ -0,0 +1,72 @@
+/* $Id: DnDPath.cpp $ */
+/** @file
+ * DnD - Path handling.
+ */
+
+/*
+ * Copyright (C) 2014-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_GUEST_DND
+#include <VBox/GuestHost/DragAndDrop.h>
+
+#include <iprt/err.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+
+/**
+ * Sanitizes the file name component so that unsupported characters
+ * will be replaced by an underscore ("_").
+ *
+ * @return IPRT status code.
+ * @param pszPath Path to sanitize.
+ * @param cbPath Size (in bytes) of path to sanitize.
+ */
+int DnDPathSanitizeFilename(char *pszPath, size_t cbPath)
+{
+ int rc = VINF_SUCCESS;
+#ifdef RT_OS_WINDOWS
+ RT_NOREF1(cbPath);
+ /* Replace out characters not allowed on Windows platforms, put in by RTTimeSpecToString(). */
+ /** @todo Use something like RTPathSanitize() if available later some time. */
+ static const RTUNICP s_uszValidRangePairs[] =
+ {
+ ' ', ' ',
+ '(', ')',
+ '-', '.',
+ '0', '9',
+ 'A', 'Z',
+ 'a', 'z',
+ '_', '_',
+ 0xa0, 0xd7af,
+ '\0'
+ };
+ ssize_t cReplaced = RTStrPurgeComplementSet(pszPath, s_uszValidRangePairs, '_' /* chReplacement */);
+ if (cReplaced < 0)
+ rc = VERR_INVALID_UTF8_ENCODING;
+#else
+ RT_NOREF2(pszPath, cbPath);
+#endif
+ return rc;
+}
+
+int DnDPathSanitize(char *pszPath, size_t cbPath)
+{
+ /** @todo */
+ RT_NOREF2(pszPath, cbPath);
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/GuestHost/DragAndDrop/DnDURIList.cpp b/src/VBox/GuestHost/DragAndDrop/DnDURIList.cpp
new file mode 100644
index 00000000..0013d760
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDURIList.cpp
@@ -0,0 +1,565 @@
+/* $Id: DnDURIList.cpp $ */
+/** @file
+ * DnD - URI list class.
+ */
+
+/*
+ * Copyright (C) 2014-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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/symlink.h>
+#include <iprt/uri.h>
+
+#include <VBox/log.h>
+
+
+DnDURIList::DnDURIList(void)
+ : m_cTotal(0)
+ , m_cbTotal(0)
+{
+}
+
+DnDURIList::~DnDURIList(void)
+{
+ Clear();
+}
+
+int DnDURIList::addEntry(const char *pcszSource, const char *pcszTarget, DNDURILISTFLAGS fFlags)
+{
+ AssertPtrReturn(pcszSource, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcszTarget, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pcszSource=%s, pcszTarget=%s, fFlags=0x%x\n", pcszSource, pcszTarget, fFlags));
+
+ RTFSOBJINFO objInfo;
+ int rc = RTPathQueryInfo(pcszSource, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ LogFlowFunc(("File '%s' -> '%s' (%RU64 bytes, file mode 0x%x)\n",
+ pcszSource, pcszTarget, (uint64_t)objInfo.cbObject, objInfo.Attr.fMode));
+
+ DnDURIObject *pObjFile = new DnDURIObject(DnDURIObject::Type_File, pcszSource, pcszTarget);
+ if (pObjFile)
+ {
+ if (fFlags & DNDURILIST_FLAGS_KEEP_OPEN) /* Shall we keep the file open while being added to this list? */
+ {
+ /** @todo Add a standard fOpen mode for this list. */
+ rc = pObjFile->Open(DnDURIObject::View_Source, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
+ }
+ else /* Just query the information without opening the file. */
+ rc = pObjFile->QueryInfo(DnDURIObject::View_Source);
+
+ if (RT_SUCCESS(rc))
+ {
+ m_lstTree.append(pObjFile);
+
+ m_cTotal++;
+ m_cbTotal += pObjFile->GetSize();
+ }
+ else
+ delete pObjFile;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ LogFlowFunc(("Directory '%s' -> '%s' (file mode 0x%x)\n", pcszSource, pcszTarget, objInfo.Attr.fMode));
+
+ DnDURIObject *pObjDir = new DnDURIObject(DnDURIObject::Type_Directory, pcszSource, pcszTarget);
+ if (pObjDir)
+ {
+ m_lstTree.append(pObjDir);
+
+ /** @todo Add DNDURILIST_FLAGS_KEEP_OPEN handling? */
+ m_cTotal++;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ /* Note: Symlinks already should have been resolved at this point. */
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+int DnDURIList::appendPathRecursive(const char *pcszSrcPath,
+ const char *pcszDstPath, const char *pcszDstBase, size_t cchDstBase,
+ DNDURILISTFLAGS fFlags)
+{
+ AssertPtrReturn(pcszSrcPath, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcszDstBase, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcszDstPath, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pcszSrcPath=%s, pcszDstPath=%s, pcszDstBase=%s, cchDstBase=%zu, fFlags=0x%x\n",
+ pcszSrcPath, pcszDstPath, pcszDstBase, cchDstBase, fFlags));
+
+ RTFSOBJINFO objInfo;
+ int rc = RTPathQueryInfo(pcszSrcPath, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ rc = addEntry(pcszSrcPath, &pcszDstPath[cchDstBase], fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ RTDIR hDir;
+ rc = RTDirOpen(&hDir, pcszSrcPath);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbDirEntry = 0;
+ PRTDIRENTRYEX pDirEntry = NULL;
+ do
+ {
+ /* Retrieve the next directory entry. */
+ rc = RTDirReadExA(hDir, &pDirEntry, &cbDirEntry, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ /* Skip "." and ".." entries. */
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ break;
+
+ char *pszSrc = RTPathJoinA(pcszSrcPath, pDirEntry->szName);
+ if (pszSrc)
+ {
+ char *pszDst = RTPathJoinA(pcszDstPath, pDirEntry->szName);
+ if (pszDst)
+ {
+ rc = appendPathRecursive(pszSrc, pszDst, pcszDstBase, cchDstBase, fFlags);
+ RTStrFree(pszDst);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTStrFree(pszSrc);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ char *pszSrc = RTPathJoinA(pcszSrcPath, pDirEntry->szName);
+ if (pszSrc)
+ {
+ char *pszDst = RTPathJoinA(pcszDstPath, pDirEntry->szName);
+ if (pszDst)
+ {
+ rc = addEntry(pszSrc, &pszDst[cchDstBase], fFlags);
+ RTStrFree(pszDst);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ RTStrFree(pszSrc);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ case RTFS_TYPE_SYMLINK:
+ {
+ if (fFlags & DNDURILIST_FLAGS_RESOLVE_SYMLINKS)
+ {
+ char *pszSrc = RTPathRealDup(pcszDstBase);
+ if (pszSrc)
+ {
+ rc = RTPathQueryInfo(pszSrc, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ LogFlowFunc(("Directory entry is symlink to directory\n"));
+ rc = appendPathRecursive(pszSrc, pcszDstPath, pcszDstBase, cchDstBase, fFlags);
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ LogFlowFunc(("Directory entry is symlink to file\n"));
+ rc = addEntry(pszSrc, &pcszDstPath[cchDstBase], fFlags);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ RTStrFree(pszSrc);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ } while (RT_SUCCESS(rc));
+
+ RTDirReadExAFree(&pDirEntry, &cbDirEntry);
+ RTDirClose(hDir);
+ }
+ }
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ rc = addEntry(pcszSrcPath, &pcszDstPath[cchDstBase], fFlags);
+ }
+ else if (RTFS_IS_SYMLINK(objInfo.Attr.fMode))
+ {
+ if (fFlags & DNDURILIST_FLAGS_RESOLVE_SYMLINKS)
+ {
+ char *pszSrc = RTPathRealDup(pcszSrcPath);
+ if (pszSrc)
+ {
+ rc = RTPathQueryInfo(pszSrc, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ LogFlowFunc(("Symlink to directory\n"));
+ rc = appendPathRecursive(pszSrc, pcszDstPath, pcszDstBase, cchDstBase, fFlags);
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ LogFlowFunc(("Symlink to file\n"));
+ rc = addEntry(pszSrc, &pcszDstPath[cchDstBase], fFlags);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ RTStrFree(pszSrc);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+int DnDURIList::AppendNativePath(const char *pszPath, DNDURILISTFLAGS fFlags)
+{
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+
+ int rc;
+ char *pszPathNative = RTStrDup(pszPath);
+ if (pszPathNative)
+ {
+ RTPathChangeToUnixSlashes(pszPathNative, true /* fForce */);
+
+ char *pszPathURI = RTUriCreate("file" /* pszScheme */, NULL /* pszAuthority */,
+ pszPathNative, NULL /* pszQuery */, NULL /* pszFragment */);
+ if (pszPathURI)
+ {
+ rc = AppendURIPath(pszPathURI, fFlags);
+ RTStrFree(pszPathURI);
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ RTStrFree(pszPathNative);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+int DnDURIList::AppendNativePathsFromList(const char *pszNativePaths, size_t cbNativePaths,
+ DNDURILISTFLAGS fFlags)
+{
+ AssertPtrReturn(pszNativePaths, VERR_INVALID_POINTER);
+ AssertReturn(cbNativePaths, VERR_INVALID_PARAMETER);
+
+ RTCList<RTCString> lstPaths
+ = RTCString(pszNativePaths, cbNativePaths - 1).split("\r\n");
+ return AppendNativePathsFromList(lstPaths, fFlags);
+}
+
+int DnDURIList::AppendNativePathsFromList(const RTCList<RTCString> &lstNativePaths,
+ DNDURILISTFLAGS fFlags)
+{
+ int rc = VINF_SUCCESS;
+
+ for (size_t i = 0; i < lstNativePaths.size(); i++)
+ {
+ const RTCString &strPath = lstNativePaths.at(i);
+ rc = AppendNativePath(strPath.c_str(), fFlags);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+int DnDURIList::AppendURIPath(const char *pszURI, DNDURILISTFLAGS fFlags)
+{
+ AssertPtrReturn(pszURI, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~DNDURILIST_FLAGS_VALID_MASK), VERR_INVALID_FLAGS);
+ /** @todo Check for string termination? */
+
+ RTURIPARSED Parsed;
+ int rc = RTUriParse(pszURI, &Parsed);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ char *pszSrcPath = NULL;
+
+ /* file://host.example.com/path/to/file.txt */
+ const char *pszParsedAuthority = RTUriParsedAuthority(pszURI, &Parsed);
+ if ( pszParsedAuthority
+ && pszParsedAuthority[0] != '\0') /* Authority present? */
+ {
+ const char *pszParsedPath = RTUriParsedPath(pszURI, &Parsed);
+ if (pszParsedPath)
+ {
+ /* Always use UNIXy paths internally. */
+ if (RTStrAPrintf(&pszSrcPath, "//%s%s", pszParsedAuthority, pszParsedPath) == -1)
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ pszSrcPath = RTUriFilePath(pszURI);
+ if (!pszSrcPath)
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ LogFlowFunc(("pszURI=%s, fFlags=0x%x -> pszParsedAuthority=%s, pszSrcPath=%s, rc=%Rrc\n",
+ pszURI, fFlags, pszParsedAuthority ? pszParsedAuthority : "<None>", pszSrcPath, rc));
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Add the path to our internal file list (recursive in
+ * the case of a directory). */
+ size_t cbPathLen = RTPathStripTrailingSlash(pszSrcPath);
+ if (cbPathLen)
+ {
+ char *pszFileName = RTPathFilename(pszSrcPath);
+ if (pszFileName)
+ {
+ Assert(pszFileName >= pszSrcPath);
+ size_t cchDstBase = (fFlags & DNDURILIST_FLAGS_ABSOLUTE_PATHS)
+ ? 0 /* Use start of path as root. */
+ : pszFileName - pszSrcPath;
+ char *pszDstPath = &pszSrcPath[cchDstBase];
+ m_lstRoot.append(pszDstPath);
+
+ LogFlowFunc(("pszSrcPath=%s, pszFileName=%s, pszRoot=%s\n",
+ pszSrcPath, pszFileName, pszDstPath));
+
+ rc = appendPathRecursive(pszSrcPath, pszSrcPath, pszSrcPath, cchDstBase, fFlags);
+ }
+ else
+ rc = VERR_PATH_NOT_FOUND;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ RTStrFree(pszSrcPath);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+int DnDURIList::AppendURIPathsFromList(const char *pszURIPaths, size_t cbURIPaths,
+ DNDURILISTFLAGS fFlags)
+{
+ AssertPtrReturn(pszURIPaths, VERR_INVALID_POINTER);
+ AssertReturn(cbURIPaths, VERR_INVALID_PARAMETER);
+
+ RTCList<RTCString> lstPaths
+ = RTCString(pszURIPaths, cbURIPaths - 1).split("\r\n");
+ return AppendURIPathsFromList(lstPaths, fFlags);
+}
+
+int DnDURIList::AppendURIPathsFromList(const RTCList<RTCString> &lstURI,
+ DNDURILISTFLAGS fFlags)
+{
+ int rc = VINF_SUCCESS;
+
+ for (size_t i = 0; i < lstURI.size(); i++)
+ {
+ RTCString strURI = lstURI.at(i);
+ rc = AppendURIPath(strURI.c_str(), fFlags);
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+void DnDURIList::Clear(void)
+{
+ m_lstRoot.clear();
+
+ for (size_t i = 0; i < m_lstTree.size(); i++)
+ {
+ DnDURIObject *pCurObj = m_lstTree.at(i);
+ AssertPtr(pCurObj);
+ RTMemFree(pCurObj);
+ }
+ m_lstTree.clear();
+
+ m_cTotal = 0;
+ m_cbTotal = 0;
+}
+
+void DnDURIList::RemoveFirst(void)
+{
+ if (m_lstTree.isEmpty())
+ return;
+
+ DnDURIObject *pCurObj = m_lstTree.first();
+ AssertPtr(pCurObj);
+
+ uint64_t cbSize = pCurObj->GetSize();
+ Assert(m_cbTotal >= cbSize);
+ m_cbTotal -= cbSize; /* Adjust total size. */
+
+ pCurObj->Close();
+ RTMemFree(pCurObj);
+
+ m_lstTree.removeFirst();
+}
+
+int DnDURIList::SetFromURIData(const void *pvData, size_t cbData, DNDURILISTFLAGS fFlags)
+{
+ Assert(fFlags == 0); RT_NOREF1(fFlags);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ if (!RTStrIsValidEncoding(static_cast<const char *>(pvData)))
+ return VERR_INVALID_PARAMETER;
+
+ RTCList<RTCString> lstURI =
+ RTCString(static_cast<const char *>(pvData), cbData - 1).split("\r\n");
+ if (lstURI.isEmpty())
+ return VINF_SUCCESS;
+
+ int rc = VINF_SUCCESS;
+
+ for (size_t i = 0; i < lstURI.size(); ++i)
+ {
+ /* Query the path component of a file URI. If this hasn't a
+ * file scheme, NULL is returned. */
+ const char *pszURI = lstURI.at(i).c_str();
+ char *pszFilePath = RTUriFilePath(pszURI);
+#ifdef DEBUG_andy
+ LogFlowFunc(("pszURI=%s, pszFilePath=%s\n", pszURI, pszFilePath));
+#endif
+ if (pszFilePath)
+ {
+ rc = DnDPathSanitize(pszFilePath, strlen(pszFilePath));
+ if (RT_SUCCESS(rc))
+ {
+ m_lstRoot.append(pszFilePath);
+ m_cTotal++;
+ }
+
+ RTStrFree(pszFilePath);
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ return rc;
+}
+
+RTCString DnDURIList::GetRootEntries(const RTCString &strPathBase /* = "" */,
+ const RTCString &strSeparator /* = "\r\n" */) const
+{
+ RTCString strRet;
+ for (size_t i = 0; i < m_lstRoot.size(); i++)
+ {
+ const char *pszCurRoot = m_lstRoot.at(i).c_str();
+#ifdef DEBUG_andy
+ LogFlowFunc(("pszCurRoot=%s\n", pszCurRoot));
+#endif
+ if (strPathBase.isNotEmpty())
+ {
+ char *pszPath = RTPathJoinA(strPathBase.c_str(), pszCurRoot);
+ if (pszPath)
+ {
+ char *pszPathURI = RTUriFileCreate(pszPath);
+ if (pszPathURI)
+ {
+ strRet += RTCString(pszPathURI) + strSeparator;
+ LogFlowFunc(("URI (Base): %s\n", strRet.c_str()));
+ RTStrFree(pszPathURI);
+ }
+
+ RTStrFree(pszPath);
+
+ if (!pszPathURI)
+ break;
+ }
+ else
+ break;
+ }
+ else
+ {
+ char *pszPathURI = RTUriFileCreate(pszCurRoot);
+ if (pszPathURI)
+ {
+ strRet += RTCString(pszPathURI) + strSeparator;
+ LogFlowFunc(("URI: %s\n", strRet.c_str()));
+ RTStrFree(pszPathURI);
+ }
+ else
+ break;
+ }
+ }
+
+ return strRet;
+}
+
diff --git a/src/VBox/GuestHost/DragAndDrop/DnDURIObject.cpp b/src/VBox/GuestHost/DragAndDrop/DnDURIObject.cpp
new file mode 100644
index 00000000..6c764fb2
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/DnDURIObject.cpp
@@ -0,0 +1,565 @@
+/* $Id: DnDURIObject.cpp $ */
+/** @file
+ * DnD - URI object class. For handling creation/reading/writing to files and directories on host or guest side.
+ */
+
+/*
+ * Copyright (C) 2014-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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/uri.h>
+
+#include <VBox/log.h>
+
+
+DnDURIObject::DnDURIObject(void)
+ : m_enmType(Type_Unknown)
+ , m_enmView(View_Unknown)
+ , m_fIsOpen(false)
+{
+ RT_ZERO(u);
+}
+
+DnDURIObject::DnDURIObject(Type enmType,
+ const RTCString &strSrcPathAbs /* = 0 */,
+ const RTCString &strDstPathAbs /* = 0 */)
+ : m_enmType(enmType)
+ , m_enmView(View_Unknown)
+ , m_strSrcPathAbs(strSrcPathAbs)
+ , m_strTgtPathAbs(strDstPathAbs)
+ , m_fIsOpen(false)
+{
+ RT_ZERO(u);
+}
+
+DnDURIObject::~DnDURIObject(void)
+{
+ closeInternal();
+}
+
+/**
+ * Closes the object's internal handles (to files / ...).
+ *
+ */
+void DnDURIObject::closeInternal(void)
+{
+ LogFlowThisFuncEnter();
+
+ if (!m_fIsOpen)
+ return;
+
+ switch (m_enmType)
+ {
+ case Type_File:
+ {
+ RTFileClose(u.File.hFile);
+ u.File.hFile = NIL_RTFILE;
+ RT_ZERO(u.File.objInfo);
+ break;
+ }
+
+ case Type_Directory:
+ {
+ RTDirClose(u.Dir.hDir);
+ u.Dir.hDir = NIL_RTDIR;
+ RT_ZERO(u.Dir.objInfo);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ m_fIsOpen = false;
+}
+
+/**
+ * Closes the object.
+ * This also closes the internal handles associated with the object (to files / ...).
+ */
+void DnDURIObject::Close(void)
+{
+ closeInternal();
+}
+
+/**
+ * Returns the directory / file mode of the object.
+ *
+ * @return File / directory mode.
+ */
+RTFMODE DnDURIObject::GetMode(void) const
+{
+ switch (m_enmType)
+ {
+ case Type_File:
+ return u.File.objInfo.Attr.fMode;
+
+ case Type_Directory:
+ return u.Dir.objInfo.Attr.fMode;
+
+ default:
+ break;
+ }
+
+ AssertFailed();
+ return 0;
+}
+
+/**
+ * Returns the bytes already processed (read / written).
+ *
+ * Note: Only applies if the object is of type DnDURIObject::Type_File.
+ *
+ * @return Bytes already processed (read / written).
+ */
+uint64_t DnDURIObject::GetProcessed(void) const
+{
+ if (m_enmType == Type_File)
+ return u.File.cbProcessed;
+
+ return 0;
+}
+
+/**
+ * Returns the file's logical size (in bytes).
+ *
+ * Note: Only applies if the object is of type DnDURIObject::Type_File.
+ *
+ * @return The file's logical size (in bytes).
+ */
+uint64_t DnDURIObject::GetSize(void) const
+{
+ if (m_enmType == Type_File)
+ return u.File.cbToProcess;
+
+ return 0;
+}
+
+/**
+ * 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.
+ */
+bool DnDURIObject::IsComplete(void) const
+{
+ bool fComplete;
+
+ switch (m_enmType)
+ {
+ case Type_File:
+ Assert(u.File.cbProcessed <= u.File.cbToProcess);
+ fComplete = u.File.cbProcessed == u.File.cbToProcess;
+ break;
+
+ case Type_Directory:
+ fComplete = true;
+ break;
+
+ default:
+ fComplete = true;
+ break;
+ }
+
+ return fComplete;
+}
+
+/**
+ * Returns whether the object is in an open state or not.
+ */
+bool DnDURIObject::IsOpen(void) const
+{
+ return m_fIsOpen;
+}
+
+/**
+ * (Re-)Opens the object with a specific view, open and file mode.
+ *
+ * @return IPRT status code.
+ * @param enmView View to use for opening the object.
+ * @param fOpen File open flags to use.
+ * @param fMode File mode to use.
+ */
+int DnDURIObject::Open(View enmView, uint64_t fOpen /* = 0 */, RTFMODE fMode /* = 0 */)
+{
+ return OpenEx( enmView == View_Source
+ ? m_strSrcPathAbs : m_strTgtPathAbs
+ , enmView, fOpen, fMode, DNDURIOBJECT_FLAGS_NONE);
+}
+
+/**
+ * Open the object with a specific file type, and, depending on the type, specifying additional parameters.
+ *
+ * @return IPRT status code.
+ * @param strPathAbs Absolute path of the object (file / directory / ...).
+ * @param enmView View of the object.
+ * @param fOpen Open mode to use; only valid for file objects.
+ * @param fMode File mode to use; only valid for file objects.
+ * @param fFlags Additional DnD URI object flags.
+ */
+int DnDURIObject::OpenEx(const RTCString &strPathAbs, View enmView,
+ uint64_t fOpen /* = 0 */, RTFMODE fMode /* = 0 */, DNDURIOBJECTFLAGS fFlags /* = DNDURIOBJECT_FLAGS_NONE */)
+{
+ AssertReturn(!(fFlags & ~DNDURIOBJECT_FLAGS_VALID_MASK), VERR_INVALID_FLAGS);
+ RT_NOREF1(fFlags);
+
+ if (m_fIsOpen)
+ return VINF_SUCCESS;
+
+ int rc = VINF_SUCCESS;
+
+ switch (enmView)
+ {
+ case View_Source:
+ m_strSrcPathAbs = strPathAbs;
+ break;
+
+ case View_Target:
+ m_strTgtPathAbs = strPathAbs;
+ break;
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && fOpen) /* Opening mode specified? */
+ {
+ LogFlowThisFunc(("strPath=%s, enmView=%RU32, fOpen=0x%x, fMode=0x%x, fFlags=0x%x\n",
+ strPathAbs.c_str(), enmView, fOpen, fMode, fFlags));
+ switch (m_enmType)
+ {
+ case Type_File:
+ {
+ /*
+ * 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.
+ */
+ LogFlowThisFunc(("Opening ...\n"));
+ rc = RTFileOpen(&u.File.hFile, strPathAbs.c_str(), 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(u.File.hFile, fMode);
+ }
+ else if (fOpen & RTFILE_O_READ)
+ {
+ rc = queryInfoInternal(enmView);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowThisFunc(("File cbObject=%RU64, fMode=0x%x\n",
+ u.File.objInfo.cbObject, u.File.objInfo.Attr.fMode));
+ u.File.cbToProcess = u.File.objInfo.cbObject;
+ u.File.cbProcessed = 0;
+ }
+
+ break;
+ }
+
+ case Type_Directory:
+ {
+ rc = RTDirOpen(&u.Dir.hDir, strPathAbs.c_str());
+ if (RT_SUCCESS(rc))
+ rc = queryInfoInternal(enmView);
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ m_enmView = enmView;
+ m_fIsOpen = true;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Queries information about the object using a specific view, internal version.
+ *
+ * @return IPRT status code.
+ * @param enmView View to use for querying information.
+ */
+int DnDURIObject::queryInfoInternal(View enmView)
+{
+ RT_NOREF(enmView);
+
+ int rc;
+
+ switch (m_enmType)
+ {
+ case Type_File:
+ rc = RTFileQueryInfo(u.File.hFile, &u.File.objInfo, RTFSOBJATTRADD_NOTHING);
+ break;
+
+ case Type_Directory:
+ rc = RTDirQueryInfo(u.Dir.hDir, &u.Dir.objInfo, RTFSOBJATTRADD_NOTHING);
+ break;
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ return rc;
+}
+
+/**
+ * Queries information about the object using a specific view.
+ *
+ * @return IPRT status code.
+ * @param enmView View to use for querying information.
+ */
+int DnDURIObject::QueryInfo(View enmView)
+{
+ return queryInfoInternal(enmView);
+}
+
+/**
+ * Rebases an absolute URI path from an old path base to a new path base.
+ * This function is needed in order to transform path from the source side to the target side.
+ *
+ * @return IPRT status code.
+ * @param strPathAbs Absolute URI path to rebase.
+ * @param strBaseOld Old base path to rebase from.
+ * @param strBaseNew New base path to rebase to.
+ *
+ ** @todo Put this into an own class like DnDURIPath : public RTCString?
+ */
+/* static */
+int DnDURIObject::RebaseURIPath(RTCString &strPathAbs,
+ const RTCString &strBaseOld /* = "" */,
+ const RTCString &strBaseNew /* = "" */)
+{
+ char *pszPath = RTUriFilePath(strPathAbs.c_str());
+ if (!pszPath) /* No URI? */
+ pszPath = RTStrDup(strPathAbs.c_str());
+
+ int rc;
+
+ if (pszPath)
+ {
+ const char *pszPathStart = pszPath;
+ const char *pszBaseOld = strBaseOld.c_str();
+ if ( pszBaseOld
+ && RTPathStartsWith(pszPath, pszBaseOld))
+ {
+ pszPathStart += strlen(pszBaseOld);
+ }
+
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ char *pszPathNew = RTPathJoinA(strBaseNew.c_str(), pszPathStart);
+ if (pszPathNew)
+ {
+ char *pszPathURI = RTUriCreate("file" /* pszScheme */, "/" /* pszAuthority */,
+ pszPathNew /* pszPath */,
+ NULL /* pszQuery */, NULL /* pszFragment */);
+ if (pszPathURI)
+ {
+ LogFlowFunc(("Rebasing \"%s\" to \"%s\"\n", strPathAbs.c_str(), pszPathURI));
+
+ strPathAbs = RTCString(pszPathURI) + "\r\n";
+ RTStrFree(pszPathURI);
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ RTStrFree(pszPathNew);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ RTStrFree(pszPath);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/**
+ * Reads data from the object. Only applies to files objects.
+ *
+ * @return IPRT status code.
+ * @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 DnDURIObject::Read(void *pvBuf, size_t cbBuf, uint32_t *pcbRead)
+{
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ /* pcbRead is optional. */
+
+ AssertMsgReturn(m_fIsOpen, ("Object not in open state\n"), VERR_INVALID_STATE);
+ AssertMsgReturn(m_enmView == View_Source, ("Cannot write to an object which is not in target view\n"),
+ VERR_INVALID_STATE);
+
+ size_t cbRead = 0;
+
+ int rc;
+ switch (m_enmType)
+ {
+ case Type_File:
+ {
+ rc = RTFileRead(u.File.hFile, pvBuf, cbBuf, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ u.File.cbProcessed += cbRead;
+ Assert(u.File.cbProcessed <= u.File.cbToProcess);
+
+ /* End of file reached or error occurred? */
+ if ( u.File.cbToProcess
+ && u.File.cbProcessed == u.File.cbToProcess)
+ {
+ rc = VINF_EOF;
+ }
+ }
+ break;
+ }
+
+ case Type_Directory:
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcbRead)
+ *pcbRead = (uint32_t)cbRead;
+ }
+
+ LogFlowFunc(("Returning strSourcePath=%s, cbRead=%zu, rc=%Rrc\n", m_strSrcPathAbs.c_str(), cbRead, rc));
+ return rc;
+}
+
+/**
+ * Resets the object's state and closes all related handles.
+ */
+void DnDURIObject::Reset(void)
+{
+ LogFlowThisFuncEnter();
+
+ Close();
+
+ m_enmType = Type_Unknown;
+ m_enmView = View_Unknown;
+ m_strSrcPathAbs = "";
+ m_strTgtPathAbs = "";
+
+ RT_ZERO(u);
+}
+
+/**
+ * Sets the bytes to process by the object.
+ *
+ * Note: Only applies if the object is of type DnDURIObject::Type_File.
+ *
+ * @return IPRT return code.
+ * @param cbSize Size (in bytes) to process.
+ */
+int DnDURIObject::SetSize(uint64_t cbSize)
+{
+ AssertReturn(m_enmType == Type_File, VERR_INVALID_PARAMETER);
+
+ /** @todo Implement sparse file support here. */
+
+ u.File.cbToProcess = cbSize;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes data to an object. Only applies to file objects.
+ *
+ * @return IPRT status code.
+ * @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 DnDURIObject::Write(const void *pvBuf, size_t cbBuf, uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ /* pcbWritten is optional. */
+
+ AssertMsgReturn(m_fIsOpen, ("Object not in open state\n"), VERR_INVALID_STATE);
+ AssertMsgReturn(m_enmView == View_Target, ("Cannot write to an object which is not in target view\n"),
+ VERR_INVALID_STATE);
+
+ size_t cbWritten = 0;
+
+ int rc;
+ switch (m_enmType)
+ {
+ case Type_File:
+ {
+ rc = RTFileWrite(u.File.hFile, pvBuf, cbBuf, &cbWritten);
+ if (RT_SUCCESS(rc))
+ u.File.cbProcessed += cbWritten;
+ break;
+ }
+
+ case Type_Directory:
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcbWritten)
+ *pcbWritten = (uint32_t)cbWritten;
+ }
+
+ LogFlowThisFunc(("Returning strSourcePathAbs=%s, cbWritten=%zu, rc=%Rrc\n", m_strSrcPathAbs.c_str(), cbWritten, rc));
+ return rc;
+}
+
diff --git a/src/VBox/GuestHost/DragAndDrop/Makefile.kmk b/src/VBox/GuestHost/DragAndDrop/Makefile.kmk
new file mode 100644
index 00000000..457c3426
--- /dev/null
+++ b/src/VBox/GuestHost/DragAndDrop/Makefile.kmk
@@ -0,0 +1,59 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the shared DnD code for both, host and guest.
+#
+
+#
+# Copyright (C) 2014-2019 Oracle Corporation
+#
+# This file is part of VirtualBox Open Source Edition (OSE), as
+# available from http://www.virtualbox.org. This file is free software;
+# you can redistribute it and/or modify it under the terms of the GNU
+# General Public License (GPL) as published by the Free Software
+# Foundation, in version 2 as it comes in the "COPYING" file of the
+# VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+VBOX_DND_GUESTHOST_FILES := \
+ DnDDroppedFiles.cpp \
+ DnDMIME.cpp \
+ DnDPath.cpp \
+ DnDURIList.cpp \
+ DnDURIObject.cpp
+
+#
+# DnDGuestR3Lib - For tools on the guest side,
+# e.g. VBoxClient/VBoxTray.
+#
+ifdef VBOX_WITH_ADDITIONS
+ LIBRARIES += VBoxDnDGuestR3Lib
+ VBoxDnDGuestR3Lib_TEMPLATE = VBOXGUESTR3LIB
+ VBoxDnDGuestR3Lib_DEFS =
+ VBoxDnDGuestR3Lib_SOURCES = $(VBOX_DND_GUESTHOST_FILES)
+
+# LIBRARIES.win.amd64 += VBoxDnDGuestR3Lib-x86
+# VBoxDnDGuestR3Lib-x86_EXTENDS := VBoxDnDGuestR3Lib
+# VBoxDnDGuestR3Lib-x86_BLD_TRG_ARCH := x86
+endif
+
+#
+# DnDHostR3Lib - For the host side, e.g. Main
+# and frontends.
+#
+LIBRARIES += VBoxDnDHostR3Lib
+VBoxDnDHostR3Lib_TEMPLATE = VBOXR3
+VBoxDnDHostR3Lib_DEFS =
+VBoxDnDHostR3Lib_SOURCES = $(VBOX_DND_GUESTHOST_FILES)
+
+# Include the common host services code.
+VBOX_PATH_HOSTSERVICES_COMMON = $(PATH_ROOT)/src/VBox/HostServices/common
+VBoxDnDHostR3Lib_SOURCES += \
+ $(VBOX_PATH_HOSTSERVICES_COMMON)/client.cpp \
+ $(VBOX_PATH_HOSTSERVICES_COMMON)/message.cpp
+
+include $(FILE_KBUILD_SUB_FOOTER)
+