/* $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--; }