From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/GuestHost/DragAndDrop/DnDDroppedFiles.cpp | 450 +++++++ src/VBox/GuestHost/DragAndDrop/DnDMIME.cpp | 63 + src/VBox/GuestHost/DragAndDrop/DnDPath.cpp | 212 ++++ src/VBox/GuestHost/DragAndDrop/DnDTransferList.cpp | 1295 ++++++++++++++++++++ .../GuestHost/DragAndDrop/DnDTransferObject.cpp | 706 +++++++++++ src/VBox/GuestHost/DragAndDrop/DnDUtils.cpp | 177 +++ src/VBox/GuestHost/DragAndDrop/Makefile.kmk | 68 + .../GuestHost/DragAndDrop/testcase/Makefile.kmk | 81 ++ .../GuestHost/DragAndDrop/testcase/tstDnDPath.cpp | 100 ++ .../DragAndDrop/testcase/tstDnDTransferList.cpp | 206 ++++ .../DragAndDrop/testcase/tstDnDTransferObject.cpp | 128 ++ .../testcase/tstDragAndDropQt/Makefile.kmk | 53 + .../testcase/tstDragAndDropQt/tstDragAndDropQt.cpp | 67 + 13 files changed, 3606 insertions(+) create mode 100644 src/VBox/GuestHost/DragAndDrop/DnDDroppedFiles.cpp create mode 100644 src/VBox/GuestHost/DragAndDrop/DnDMIME.cpp create mode 100644 src/VBox/GuestHost/DragAndDrop/DnDPath.cpp create mode 100644 src/VBox/GuestHost/DragAndDrop/DnDTransferList.cpp create mode 100644 src/VBox/GuestHost/DragAndDrop/DnDTransferObject.cpp create mode 100644 src/VBox/GuestHost/DragAndDrop/DnDUtils.cpp create mode 100644 src/VBox/GuestHost/DragAndDrop/Makefile.kmk create mode 100644 src/VBox/GuestHost/DragAndDrop/testcase/Makefile.kmk create mode 100644 src/VBox/GuestHost/DragAndDrop/testcase/tstDnDPath.cpp create mode 100644 src/VBox/GuestHost/DragAndDrop/testcase/tstDnDTransferList.cpp create mode 100644 src/VBox/GuestHost/DragAndDrop/testcase/tstDnDTransferObject.cpp create mode 100644 src/VBox/GuestHost/DragAndDrop/testcase/tstDragAndDropQt/Makefile.kmk create mode 100644 src/VBox/GuestHost/DragAndDrop/testcase/tstDragAndDropQt/tstDragAndDropQt.cpp (limited to 'src/VBox/GuestHost/DragAndDrop') 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_GUEST_DND +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_GUEST_DND +#include + +#include + + +/** + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_GUEST_DND +#include + +#include +#include +#include +#include +#include +#include + + +/** + * 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 . + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 : "", 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 : "")); + + 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_GUEST_DND +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include +#include + +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 "" 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, ""), 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 . +# +# 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 . +# +# 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include + +#include + + +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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include + +#include + + +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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include + +#include + + +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 . +# +# 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include + +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(); +} -- cgit v1.2.3