From 6b0f2d978504da82070523eb6adb0b59f9812e93 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 3 Jun 2022 17:48:39 -0500 Subject: Add PathSkipPastRoot. --- src/burn/engine/bundlepackageengine.cpp | 2 +- src/burn/engine/exeengine.cpp | 2 +- src/burn/engine/logging.cpp | 6 +- src/libs/dutil/WixToolset.DUtil/dirutil.cpp | 20 +- src/libs/dutil/WixToolset.DUtil/inc/pathutil.h | 55 +++- src/libs/dutil/WixToolset.DUtil/logutil.cpp | 2 +- src/libs/dutil/WixToolset.DUtil/path2utl.cpp | 53 ++-- src/libs/dutil/WixToolset.DUtil/pathutil.cpp | 296 +++++++++++++-------- src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp | 288 ++++++++++---------- 9 files changed, 414 insertions(+), 310 deletions(-) (limited to 'src') diff --git a/src/burn/engine/bundlepackageengine.cpp b/src/burn/engine/bundlepackageengine.cpp index 97861436..8ba8e0d2 100644 --- a/src/burn/engine/bundlepackageengine.cpp +++ b/src/burn/engine/bundlepackageengine.cpp @@ -763,7 +763,7 @@ static HRESULT ExecuteBundle( if (fPseudoPackage) { - if (!PathIsFullyQualified(pPackagePayload->sczFilePath, NULL)) + if (!PathIsFullyQualified(pPackagePayload->sczFilePath)) { ExitWithRootFailure(hr, E_INVALIDSTATE, "Related bundles must have a fully qualified target path."); } diff --git a/src/burn/engine/exeengine.cpp b/src/burn/engine/exeengine.cpp index c3757e92..fb852c78 100644 --- a/src/burn/engine/exeengine.cpp +++ b/src/burn/engine/exeengine.cpp @@ -363,7 +363,7 @@ extern "C" HRESULT ExeEngineExecutePackage( if (pPackage->Exe.fPseudoPackage && BURN_PAYLOAD_VERIFICATION_UPDATE_BUNDLE != pPackagePayload->verification) { - if (!PathIsFullyQualified(pPackagePayload->sczFilePath, NULL)) + if (!PathIsFullyQualified(pPackagePayload->sczFilePath)) { ExitWithRootFailure(hr, E_INVALIDSTATE, "Pseudo ExePackages must have a fully qualified target path."); } diff --git a/src/burn/engine/logging.cpp b/src/burn/engine/logging.cpp index 3a403025..77f5079c 100644 --- a/src/burn/engine/logging.cpp +++ b/src/burn/engine/logging.cpp @@ -134,10 +134,12 @@ extern "C" HRESULT LoggingOpen( if (sczPrefixFormatted && *sczPrefixFormatted) { + // Best effort to open default logging. LPCWSTR wzPrefix = sczPrefixFormatted; + LPCWSTR wzPastRoot = PathSkipPastRoot(sczPrefixFormatted, NULL, NULL, NULL); - // Best effort to open default logging. - if (PathIsRooted(sczPrefixFormatted)) + // If the log path is rooted and has a file component, then use that path as is. + if (wzPastRoot && *wzPastRoot) { hr = PathGetDirectory(sczPrefixFormatted, &sczLoggingBaseFolder); ExitOnFailure(hr, "Failed to get parent directory from '%ls'.", sczPrefixFormatted); diff --git a/src/libs/dutil/WixToolset.DUtil/dirutil.cpp b/src/libs/dutil/WixToolset.DUtil/dirutil.cpp index c106a467..94eab9e7 100644 --- a/src/libs/dutil/WixToolset.DUtil/dirutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/dirutil.cpp @@ -364,17 +364,27 @@ extern "C" DWORD DAPI DirDeleteEmptyDirectoriesToRoot( __in DWORD /*dwFlags*/ ) { + HRESULT hr = S_OK; DWORD cDeletedDirs = 0; LPWSTR sczPath = NULL; + LPCWSTR wzPastRoot = NULL; + SIZE_T cchRoot = 0; + + // Make sure the path is normalized and prefixed. + hr = PathExpand(&sczPath, wzPath, PATH_EXPAND_FULLPATH); + DirExitOnFailure(hr, "Failed to get full path for: %ls", wzPath); + + wzPastRoot = PathSkipPastRoot(sczPath, NULL, NULL, NULL); + DirExitOnNull(wzPastRoot, hr, E_INVALIDARG, "Full path was not rooted: %ls", sczPath); - while (wzPath && *wzPath && ::RemoveDirectoryW(wzPath)) + cchRoot = wzPastRoot - sczPath; + + while (sczPath && sczPath[cchRoot] && ::RemoveDirectoryW(sczPath)) { ++cDeletedDirs; - HRESULT hr = PathGetParentPath(wzPath, &sczPath); - DirExitOnFailure(hr, "Failed to get parent directory for path: %ls", wzPath); - - wzPath = sczPath; + hr = PathGetParentPath(sczPath, &sczPath, &cchRoot); + DirExitOnFailure(hr, "Failed to get parent directory for path: %ls", sczPath); } LExit: diff --git a/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h b/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h index e64c8ef3..727318f2 100644 --- a/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h +++ b/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h @@ -8,8 +8,8 @@ extern "C" { typedef enum _PATH_CANONICALIZE { - // Always prefix fully qualified paths with the long path prefix (\\?\). - PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX = 0x0001, + // Always prefix fully qualified paths with the extended path prefix (\\?\). + PATH_CANONICALIZE_APPEND_EXTENDED_PATH_PREFIX = 0x0001, // Always terminate the path with \. PATH_CANONICALIZE_BACKSLASH_TERMINATE = 0x0002, // Don't collapse . or .. in the \\server\share portion of a UNC path. @@ -22,6 +22,14 @@ typedef enum _PATH_EXPAND PATH_EXPAND_FULLPATH = 0x0002, } PATH_EXPAND; +typedef enum _PATH_PREFIX +{ + // Add prefix even if the path is not longer than MAX_PATH. + PATH_PREFIX_SHORT_PATHS = 0x0001, + // Error with E_INVALIDARG if the path is not fully qualified. + PATH_PREFIX_EXPECT_FULLY_QUALIFIED = 0x0002, +} PATH_PREFIX; + /******************************************************************* PathFile - returns a pointer to the file part of the path. @@ -40,8 +48,9 @@ DAPI_(LPCWSTR) PathExtension( /******************************************************************* PathGetDirectory - extracts the directory from a path including the directory separator. - This means calling the function again with the previous result returns the same result. - Returns S_FALSE if the path only contains a file name. + Calling the function again with the previous result returns the same result. + Returns S_FALSE if the path only contains a file name. + For example, C:\a\b -> C:\a\ -> C:\a\ ********************************************************************/ DAPI_(HRESULT) PathGetDirectory( __in_z LPCWSTR wzPath, @@ -49,12 +58,18 @@ DAPI_(HRESULT) PathGetDirectory( ); /******************************************************************* -PathGetParentPath - extracts the parent directory from a full path. - *psczDirectory is NULL if the path only contains a file name. +PathGetParentPath - extracts the parent directory from a path + ignoring a trailing slash so that when called repeatedly, + it eventually returns the root portion of the path. + *psczDirectory is NULL if the path only contains a file name or + the path only contains the root. + *pcchRoot is the length of the root part of the path. + For example, C:\a\b -> C:\a\ -> C:\ -> NULL ********************************************************************/ DAPI_(HRESULT) PathGetParentPath( __in_z LPCWSTR wzPath, - __out_z LPWSTR *psczDirectory + __out_z LPWSTR *psczDirectory, + __out_opt SIZE_T* pcchRoot ); /******************************************************************* @@ -78,11 +93,14 @@ DAPI_(HRESULT) PathGetFullPathName( ); /******************************************************************* - PathPrefix - prefixes a full path with \\?\ or \\?\UNC as - appropriate. + PathPrefix - prefixes a path with \\?\ or \\?\UNC if it doesn't + already have an extended prefix, is longer than MAX_PATH, + and is fully qualified. ********************************************************************/ DAPI_(HRESULT) PathPrefix( - __inout LPWSTR *psczFullPath + __inout_z LPWSTR *psczFullPath, + __in SIZE_T cchFullPath, + __in DWORD dwPrefixFlags ); /******************************************************************* @@ -203,6 +221,20 @@ DAPI_(HRESULT) PathGetKnownFolder( __out LPWSTR* psczKnownFolder ); +/******************************************************************* + PathSkipPastRoot - returns a pointer to the first character after + the root portion of the path or NULL if the path has no root. + For example, the pointer will point to the "a" in "after": + C:\after, C:after, \after, \\server\share\after, + \\?\C:\afterroot, \\?\UNC\server\share\after +*******************************************************************/ +DAPI_(LPCWSTR) PathSkipPastRoot( + __in_z LPCWSTR wzPath, + __out_opt BOOL* pfHasExtendedPrefix, + __out_opt BOOL* pfFullyQualified, + __out_opt BOOL* pfUNC + ); + /******************************************************************* PathIsFullyQualified - returns true if the path is fully qualified; false otherwise. Note that some rooted paths like C:dir are not fully qualified. @@ -210,8 +242,7 @@ DAPI_(HRESULT) PathGetKnownFolder( For example, these are not fully qualified: C:dir, C:, \dir, dir, dir\subdir. *******************************************************************/ DAPI_(BOOL) PathIsFullyQualified( - __in_z LPCWSTR wzPath, - __out_opt BOOL* pfHasLongPathPrefix + __in_z LPCWSTR wzPath ); /******************************************************************* diff --git a/src/libs/dutil/WixToolset.DUtil/logutil.cpp b/src/libs/dutil/WixToolset.DUtil/logutil.cpp index 94c21374..88a90d8c 100644 --- a/src/libs/dutil/WixToolset.DUtil/logutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/logutil.cpp @@ -133,7 +133,7 @@ extern "C" HRESULT DAPI LogOpen( hr = PathConcat(wzDirectory, wzLog, &sczCombined); LoguExitOnFailure(hr, "Failed to combine the log path."); - if (!PathIsFullyQualified(sczCombined, NULL)) + if (!PathIsFullyQualified(sczCombined)) { hr = PathExpand(&LogUtil_sczLogPath, sczCombined, PATH_EXPAND_FULLPATH); LoguExitOnFailure(hr, "Failed to expand the log path."); diff --git a/src/libs/dutil/WixToolset.DUtil/path2utl.cpp b/src/libs/dutil/WixToolset.DUtil/path2utl.cpp index 1957a8c5..9e48f9d5 100644 --- a/src/libs/dutil/WixToolset.DUtil/path2utl.cpp +++ b/src/libs/dutil/WixToolset.DUtil/path2utl.cpp @@ -64,31 +64,12 @@ DAPI_(HRESULT) PathCanonicalizeForComparison( if (PATH_CANONICALIZE_KEEP_UNC_ROOT & dwCanonicalizeFlags) { - if (L'\\' == sczNormalizedPath[0] && (L'\\' == sczNormalizedPath[1] || L'?' == sczNormalizedPath[1]) && L'?' == sczNormalizedPath[2] && L'\\' == sczNormalizedPath[3]) + BOOL fUNC = FALSE; + LPCWSTR wzPastRoot = PathSkipPastRoot(sczNormalizedPath, NULL, NULL, &fUNC); + if (fUNC) { - if (L'U' == sczNormalizedPath[4] && L'N' == sczNormalizedPath[5] && L'C' == sczNormalizedPath[6] && L'\\' == sczNormalizedPath[7]) - { - cchUncRootLength = 8; - } - } - else if (L'\\' == sczNormalizedPath[0] && L'\\' == sczNormalizedPath[1]) - { - cchUncRootLength = 2; - } - - if (cchUncRootLength) - { - DWORD dwRemainingSlashes = 2; - - for (wzNormalizedPath += cchUncRootLength; *wzNormalizedPath && dwRemainingSlashes; ++wzNormalizedPath) - { - ++cchUncRootLength; - - if (L'\\' == *wzNormalizedPath) - { - --dwRemainingSlashes; - } - } + wzNormalizedPath = wzPastRoot; + cchUncRootLength = wzPastRoot - sczNormalizedPath; } } @@ -115,22 +96,24 @@ DAPI_(HRESULT) PathCanonicalizeForComparison( if (PATH_CANONICALIZE_BACKSLASH_TERMINATE & dwCanonicalizeFlags) { hr = PathBackslashTerminate(psczCanonicalized); - PathExitOnFailure(hr, "Failed to backslash terminate the canonicalized path"); + PathExitOnFailure(hr, "Failed to backslash terminate the canonicalized path."); } - if (PathIsFullyQualified(*psczCanonicalized, &fHasPrefix) && !fHasPrefix && - (PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX & dwCanonicalizeFlags)) + if (PATH_CANONICALIZE_APPEND_EXTENDED_PATH_PREFIX & dwCanonicalizeFlags) { - hr = PathPrefix(psczCanonicalized); - PathExitOnFailure(hr, "Failed to ensure the long path prefix on the canonicalized path"); - - fHasPrefix = TRUE; + hr = PathPrefix(psczCanonicalized, 0, PATH_PREFIX_SHORT_PATHS); + PathExitOnFailure(hr, "Failed to ensure the extended path prefix on the canonicalized path."); } + PathSkipPastRoot(*psczCanonicalized, &fHasPrefix, NULL, NULL); + if (fHasPrefix) { - // Canonicalize \??\ into \\?\. + // Canonicalize prefix into \\?\. + (*psczCanonicalized)[0] = L'\\'; (*psczCanonicalized)[1] = L'\\'; + (*psczCanonicalized)[2] = L'?'; + (*psczCanonicalized)[3] = L'\\'; } LExit: @@ -188,7 +171,7 @@ DAPI_(HRESULT) PathCompareCanonicalized( HRESULT hr = S_OK; LPWSTR sczCanonicalized1 = NULL; LPWSTR sczCanonicalized2 = NULL; - DWORD dwDefaultFlags = PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX | PATH_CANONICALIZE_KEEP_UNC_ROOT; + DWORD dwDefaultFlags = PATH_CANONICALIZE_APPEND_EXTENDED_PATH_PREFIX | PATH_CANONICALIZE_KEEP_UNC_ROOT; int nResult = 0; if (!wzPath1 || !wzPath2) @@ -221,7 +204,7 @@ DAPI_(HRESULT) PathDirectoryContainsPath( HRESULT hr = S_OK; LPWSTR sczCanonicalizedDirectory = NULL; LPWSTR sczCanonicalizedPath = NULL; - DWORD dwDefaultFlags = PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX | PATH_CANONICALIZE_KEEP_UNC_ROOT; + DWORD dwDefaultFlags = PATH_CANONICALIZE_APPEND_EXTENDED_PATH_PREFIX | PATH_CANONICALIZE_KEEP_UNC_ROOT; size_t cchDirectory = 0; if (!wzDirectory || !*wzDirectory) @@ -239,7 +222,7 @@ DAPI_(HRESULT) PathDirectoryContainsPath( hr = PathCanonicalizeForComparison(wzPath, dwDefaultFlags, &sczCanonicalizedPath); PathExitOnFailure(hr, "Failed to canonicalize the path."); - if (!PathIsFullyQualified(sczCanonicalizedDirectory, NULL)) + if (!PathIsFullyQualified(sczCanonicalizedDirectory)) { PathExitWithRootFailure(hr, E_INVALIDARG, "wzDirectory must be a fully qualified path."); } diff --git a/src/libs/dutil/WixToolset.DUtil/pathutil.cpp b/src/libs/dutil/WixToolset.DUtil/pathutil.cpp index abbf4d4b..1ac76626 100644 --- a/src/libs/dutil/WixToolset.DUtil/pathutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/pathutil.cpp @@ -120,13 +120,34 @@ LExit: DAPI_(HRESULT) PathGetParentPath( __in_z LPCWSTR wzPath, - __out_z LPWSTR *psczParent + __out_z LPWSTR* psczParent, + __out_opt SIZE_T* pcchRoot ) { HRESULT hr = S_OK; + LPCWSTR wzPastRoot = NULL; LPCWSTR wzParent = NULL; + LPCWSTR wz = NULL; - for (LPCWSTR wz = wzPath; *wz; ++wz) + wzPastRoot = PathSkipPastRoot(wzPath, NULL, NULL, NULL); + + if (pcchRoot) + { + *pcchRoot = !wzPastRoot ? 0 : wzPastRoot - wzPath; + } + + if (wzPastRoot && *wzPastRoot) + { + Assert(wzPastRoot > wzPath); + wz = wzPastRoot; + wzParent = wzPastRoot - 1; + } + else + { + wz = wzPath; + } + + for (; *wz; ++wz) { if (IsPathSeparatorChar(*wz) && wz[1]) { @@ -143,7 +164,7 @@ DAPI_(HRESULT) PathGetParentPath( } else { - ReleaseNullStr(psczParent); + ReleaseNullStr(*psczParent); } LExit: @@ -164,9 +185,8 @@ DAPI_(HRESULT) PathExpand( LPWSTR sczExpandedPath = NULL; SIZE_T cchWritten = 0; DWORD cchExpandedPath = 0; - SIZE_T cbSize = 0; - LPWSTR sczFullPath = NULL; + DWORD dwPrefixFlags = 0; // // First, expand any environment variables. @@ -201,20 +221,7 @@ DAPI_(HRESULT) PathExpand( } } - if (MAX_PATH < cch) - { - hr = PathPrefix(&sczExpandedPath); // ignore invald arg from path prefix because this may not be a complete path yet - if (E_INVALIDARG == hr) - { - hr = S_OK; - } - PathExitOnFailure(hr, "Failed to prefix long path after expanding environment variables."); - - hr = StrMaxLength(sczExpandedPath, &cbSize); - PathExitOnFailure(hr, "Failed to get max length of expanded path."); - - cchExpandedPath = (DWORD)min(DWORD_MAX, cbSize); - } + cchWritten = cch; } // @@ -227,11 +234,7 @@ DAPI_(HRESULT) PathExpand( hr = PathGetFullPathName(wzPath, &sczFullPath, NULL, &cchWritten); PathExitOnFailure(hr, "Failed to get full path for string: %ls", wzPath); - if (MAX_PATH < cchWritten) - { - hr = PathPrefix(&sczFullPath); - PathExitOnFailure(hr, "Failed to prefix long path after expanding."); - } + dwPrefixFlags |= PATH_PREFIX_EXPECT_FULLY_QUALIFIED; } else { @@ -239,6 +242,12 @@ DAPI_(HRESULT) PathExpand( sczExpandedPath = NULL; } + if (dwResolveFlags) + { + hr = PathPrefix(&sczFullPath, cchWritten, dwPrefixFlags); + PathExitOnFailure(hr, "Failed to prefix path after expanding."); + } + hr = StrAllocString(psczFullPath, sczFullPath ? sczFullPath : wzRelativePath, 0); PathExitOnFailure(hr, "Failed to copy relative path into full path."); @@ -319,29 +328,54 @@ LExit: DAPI_(HRESULT) PathPrefix( - __inout LPWSTR *psczFullPath + __inout_z LPWSTR* psczFullPath, + __in SIZE_T cchFullPath, + __in DWORD dwPrefixFlags ) { - Assert(psczFullPath && *psczFullPath); + Assert(psczFullPath); HRESULT hr = S_OK; LPWSTR wzFullPath = *psczFullPath; BOOL fFullyQualified = FALSE; BOOL fHasPrefix = FALSE; + BOOL fUNC = FALSE; SIZE_T cbFullPath = 0; - fFullyQualified = PathIsFullyQualified(wzFullPath, &fHasPrefix); + PathSkipPastRoot(wzFullPath, &fHasPrefix, &fFullyQualified, &fUNC); + if (fHasPrefix) { ExitFunction(); } - if (fFullyQualified && L':' == wzFullPath[1]) // normal path + // The prefix is only allowed on fully qualified paths. + if (!fFullyQualified) { - hr = StrAllocPrefix(psczFullPath, L"\\\\?\\", 4); - PathExitOnFailure(hr, "Failed to add prefix to file path."); + if (dwPrefixFlags & PATH_PREFIX_EXPECT_FULLY_QUALIFIED) + { + PathExitWithRootFailure(hr, E_INVALIDARG, "Expected fully qualified path provided to prefix: %ls.", wzFullPath); + } + + ExitFunction(); + } + + if (!(dwPrefixFlags & PATH_PREFIX_SHORT_PATHS)) + { + // The prefix is not necessary unless the path is longer than MAX_PATH. + if (!cchFullPath) + { + hr = ::StringCchLengthW(wzFullPath, STRSAFE_MAX_CCH, reinterpret_cast(&cchFullPath)); + PathExitOnFailure(hr, "Failed to get length of path to prefix."); + } + + if (MAX_PATH >= cchFullPath) + { + ExitFunction(); + } } - else if (fFullyQualified && IsPathSeparatorChar(wzFullPath[1])) // UNC + + if (fUNC) { hr = StrSize(*psczFullPath, &cbFullPath); PathExitOnFailure(hr, "Failed to get size of full path."); @@ -352,10 +386,10 @@ DAPI_(HRESULT) PathPrefix( hr = StrAllocPrefix(psczFullPath, L"\\\\?\\UNC", 7); PathExitOnFailure(hr, "Failed to add prefix to UNC path."); } - else + else // must be a normal path { - hr = E_INVALIDARG; - PathExitOnFailure(hr, "Invalid path provided to prefix: %ls.", wzFullPath); + hr = StrAllocPrefix(psczFullPath, L"\\\\?\\", 4); + PathExitOnFailure(hr, "Failed to add prefix to file path."); } LExit: @@ -970,55 +1004,114 @@ LExit: } -DAPI_(BOOL) PathIsFullyQualified( - __in_z LPCWSTR wzPath, - __out_opt BOOL* pfHasLongPathPrefix +DAPI_(LPCWSTR) PathSkipPastRoot( + __in_z_opt LPCWSTR wzPath, + __out_opt BOOL* pfHasExtendedPrefix, + __out_opt BOOL* pfFullyQualified, + __out_opt BOOL* pfUNC ) { + LPCWSTR wzPastRoot = NULL; + BOOL fHasPrefix = FALSE; BOOL fFullyQualified = FALSE; - BOOL fHasLongPathPrefix = FALSE; + BOOL fUNC = FALSE; + DWORD dwRootMissingSlashes = 0; - if (!wzPath || !wzPath[0] || !wzPath[1]) + if (!wzPath || !*wzPath) { - // There is no way to specify a fully qualified path with one character (or less). ExitFunction(); } - if (!IsPathSeparatorChar(wzPath[0])) + if (IsPathSeparatorChar(wzPath[0])) { - // The only way to specify a fully qualified path that doesn't begin with a slash - // is the drive, colon, slash format (C:\). - if (IsValidDriveChar(wzPath[0]) && - L':' == wzPath[1] && - IsPathSeparatorChar(wzPath[2])) + if (IsPathSeparatorChar(wzPath[1]) && (L'?' == wzPath[2] || L'.' == wzPath[2]) && IsPathSeparatorChar(wzPath[3]) || + L'?' == wzPath[1] && L'?' == wzPath[2] && IsPathSeparatorChar(wzPath[3])) { - fFullyQualified = TRUE; - } + fHasPrefix = TRUE; - ExitFunction(); + if (L'U' == wzPath[4] && L'N' == wzPath[5] && L'C' == wzPath[6] && IsPathSeparatorChar(wzPath[7])) + { + fUNC = TRUE; + wzPastRoot = wzPath + 8; + dwRootMissingSlashes = 2; + } + else + { + wzPastRoot = wzPath + 4; + dwRootMissingSlashes = 1; + } + } + else if (IsPathSeparatorChar(wzPath[1])) + { + fUNC = TRUE; + wzPastRoot = wzPath + 2; + dwRootMissingSlashes = 2; + } } - // Non-drive fully qualified paths must start with \\ or \?. - // \??\ is an archaic form of \\?\. - if (L'?' != wzPath[1] && !IsPathSeparatorChar(wzPath[1])) + if (dwRootMissingSlashes) { - ExitFunction(); + Assert(wzPastRoot); + fFullyQualified = TRUE; + + for (; *wzPastRoot && dwRootMissingSlashes; ++wzPastRoot) + { + if (IsPathSeparatorChar(*wzPastRoot)) + { + --dwRootMissingSlashes; + } + } } + else + { + Assert(!wzPastRoot); - fFullyQualified = TRUE; + if (IsPathSeparatorChar(wzPath[0])) + { + wzPastRoot = wzPath + 1; + } + else if (IsValidDriveChar(wzPath[0]) && wzPath[1] == L':') + { + if (IsPathSeparatorChar(wzPath[2])) + { + fFullyQualified = TRUE; + wzPastRoot = wzPath + 3; + } + else + { + wzPastRoot = wzPath + 2; + } + } + } - if (L'?' == wzPath[2] && IsPathSeparatorChar(wzPath[3])) +LExit: + if (pfHasExtendedPrefix) { - fHasLongPathPrefix = TRUE; + *pfHasExtendedPrefix = fHasPrefix; } + if (pfFullyQualified) + { + *pfFullyQualified = fFullyQualified; + } -LExit: - if (pfHasLongPathPrefix) + if (pfUNC) { - *pfHasLongPathPrefix = fHasLongPathPrefix; + *pfUNC = fUNC; } + return wzPastRoot; +} + + +DAPI_(BOOL) PathIsFullyQualified( + __in_z LPCWSTR wzPath + ) +{ + BOOL fFullyQualified = FALSE; + + PathSkipPastRoot(wzPath, NULL, &fFullyQualified, NULL); + return fFullyQualified; } @@ -1027,9 +1120,7 @@ DAPI_(BOOL) PathIsRooted( __in_z LPCWSTR wzPath ) { - return wzPath && - (IsPathSeparatorChar(wzPath[0]) || - IsValidDriveChar(wzPath[0]) && wzPath[1] == L':'); + return NULL != PathSkipPastRoot(wzPath, NULL, NULL, NULL); } @@ -1118,78 +1209,47 @@ DAPI_(HRESULT) PathGetHierarchyArray( ) { HRESULT hr = S_OK; - LPWSTR sczPathCopy = NULL; - LPWSTR sczNewPathCopy = NULL; - DWORD cArraySpacesNeeded = 0; - size_t cchPath = 0; + LPCWSTR wz = NULL; + SIZE_T cch = 0; + *pcPathArray = 0; - hr = ::StringCchLengthW(wzPath, STRSAFE_MAX_LENGTH, &cchPath); - PathExitOnRootFailure(hr, "Failed to get string length of path: %ls", wzPath); + PathExitOnNull(wzPath, hr, E_INVALIDARG, "wzPath is required."); - if (!cchPath) + wz = PathSkipPastRoot(wzPath, NULL, NULL, NULL); + if (wz) { - ExitFunction1(hr = E_INVALIDARG); - } + cch = wz - wzPath; - for (size_t i = 0; i < cchPath; ++i) - { - if (IsPathSeparatorChar(wzPath[i])) - { - ++cArraySpacesNeeded; - } - } + hr = MemEnsureArraySize(reinterpret_cast(prgsczPathArray), 1, sizeof(LPWSTR), 5); + PathExitOnFailure(hr, "Failed to allocate array."); - if (!IsPathSeparatorChar(wzPath[cchPath - 1])) - { - ++cArraySpacesNeeded; - } + hr = StrAllocString(*prgsczPathArray, wzPath, cch); + PathExitOnFailure(hr, "Failed to copy root into array."); - // If it's a UNC path, cut off the first three paths, 2 because it starts with a double backslash, and another because the first ("\\servername\") isn't a path. - if (IsPathSeparatorChar(wzPath[0]) && IsPathSeparatorChar(wzPath[1])) + *pcPathArray += 1; + } + else { - if (3 > cArraySpacesNeeded) - { - ExitFunction1(hr = E_INVALIDARG); - } - - cArraySpacesNeeded -= 3; + wz = wzPath; } - Assert(cArraySpacesNeeded >= 1); - - hr = MemEnsureArraySize(reinterpret_cast(prgsczPathArray), cArraySpacesNeeded, sizeof(LPWSTR), 0); - PathExitOnFailure(hr, "Failed to allocate array of size %u for parent directories", cArraySpacesNeeded); - *pcPathArray = cArraySpacesNeeded; - - hr = StrAllocString(&sczPathCopy, wzPath, 0); - PathExitOnFailure(hr, "Failed to allocate copy of original path"); - - for (DWORD i = 0; i < cArraySpacesNeeded; ++i) + for (; *wz; ++wz) { - hr = StrAllocString((*prgsczPathArray) + cArraySpacesNeeded - 1 - i, sczPathCopy, 0); - PathExitOnFailure(hr, "Failed to copy path"); + ++cch; - DWORD cchPathCopy = lstrlenW(sczPathCopy); - - // If it ends in a backslash, it's a directory path, so cut off everything the last backslash before we get the directory portion of the path - if (IsPathSeparatorChar(wzPath[cchPathCopy - 1])) + if (IsPathSeparatorChar(*wz) || !wz[1]) { - sczPathCopy[cchPathCopy - 1] = L'\0'; - } - - hr = PathGetDirectory(sczPathCopy, &sczNewPathCopy); - PathExitOnFailure(hr, "Failed to get directory portion of path"); + hr = MemEnsureArraySizeForNewItems(reinterpret_cast(prgsczPathArray), *pcPathArray, 1, sizeof(LPWSTR), 5); + PathExitOnFailure(hr, "Failed to allocate array."); - ReleaseStr(sczPathCopy); - sczPathCopy = sczNewPathCopy; - sczNewPathCopy = NULL; - } + hr = StrAllocString(*prgsczPathArray + *pcPathArray, wzPath, cch); + PathExitOnFailure(hr, "Failed to copy path into array."); - hr = S_OK; + *pcPathArray += 1; + } + } LExit: - ReleaseStr(sczPathCopy); - return hr; } diff --git a/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp index 2505c6bf..d1d304d3 100644 --- a/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp +++ b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp @@ -135,14 +135,22 @@ namespace DutilTests NativeAssert::Succeeded(hr, "Failed to canonicalize path"); NativeAssert::StringEqual(L"\\\\server\\share\\", sczCanonicalized); - hr = PathCanonicalizeForComparison(L"\\\\.\\share\\otherdir\\unc.exe", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized); + hr = PathCanonicalizeForComparison(L"\\\\.\\UNC\\server\\share\\..\\unc.exe", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized); NativeAssert::Succeeded(hr, "Failed to canonicalize path"); - NativeAssert::StringEqual(L"\\\\.\\share\\otherdir\\unc.exe", sczCanonicalized); + NativeAssert::StringEqual(L"\\\\?\\UNC\\server\\share\\unc.exe", sczCanonicalized); - hr = PathCanonicalizeForComparison(L"\\\\.\\share\\otherdir\\unc.exe", 0, &sczCanonicalized); + hr = PathCanonicalizeForComparison(L"\\\\..\\share\\otherdir\\unc.exe", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\..\\share\\otherdir\\unc.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"\\\\..\\share\\otherdir\\unc.exe", 0, &sczCanonicalized); NativeAssert::Succeeded(hr, "Failed to canonicalize path"); NativeAssert::StringEqual(L"\\\\share\\otherdir\\unc.exe", sczCanonicalized); + hr = PathCanonicalizeForComparison(L"\\\\.\\UNC\\share\\otherdir\\unc.exe", 0, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\UNC\\share\\otherdir\\unc.exe", sczCanonicalized); + hr = PathCanonicalizeForComparison(L"\\\\server\\share\\..\\..\\otherdir\\unc.exe", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized); NativeAssert::Succeeded(hr, "Failed to canonicalize path"); NativeAssert::StringEqual(L"\\\\server\\share\\otherdir\\unc.exe", sczCanonicalized); @@ -215,7 +223,7 @@ namespace DutilTests NativeAssert::Succeeded(hr, "Failed to canonicalize path"); NativeAssert::StringEqual(L"C:\\invalid:pathchars?.exe", sczCanonicalized); - hr = PathCanonicalizeForComparison(L"C:\\addprefix.exe", PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX, &sczCanonicalized); + hr = PathCanonicalizeForComparison(L"C:\\addprefix.exe", PATH_CANONICALIZE_APPEND_EXTENDED_PATH_PREFIX, &sczCanonicalized); NativeAssert::Succeeded(hr, "Failed to canonicalize path"); NativeAssert::StringEqual(L"\\\\?\\C:\\addprefix.exe", sczCanonicalized); @@ -312,7 +320,7 @@ namespace DutilTests LPCWSTR rgwzPaths[8] = { L"C:\\simplepath", L"D:\\simplepath", - L"\\\\.\\share\\otherdir\\unc.exe", L"\\\\share\\otherdir\\unc.exe", + L"\\\\..\\share\\otherdir\\unc.exe", L"\\\\share\\otherdir\\unc.exe", L"\\\\server\\.\\otherdir\\unc.exe", L"\\\\server\\otherdir\\unc.exe", L"\\\\server\\\\otherdir\\unc.exe", L"\\\\server\\otherdir\\unc.exe", }; @@ -541,9 +549,10 @@ namespace DutilTests { HRESULT hr = S_OK; LPWSTR sczPath = NULL; - LPCWSTR rgwzPaths[18] = + LPCWSTR rgwzPaths[20] = { L"C:\\a\\b", L"C:\\a\\", + L"C:\\a\\b\\", L"C:\\a\\b\\", L"C:\\a", L"C:\\", L"C:\\", L"C:\\", L"\"C:\\a\\b\\c\"", L"\"C:\\a\\b\\", @@ -569,6 +578,40 @@ namespace DutilTests } } + [Fact] + void PathGetParentPathTest() + { + HRESULT hr = S_OK; + LPWSTR sczPath = NULL; + LPCWSTR rgwzPaths[20] = + { + L"C:\\a\\b", L"C:\\a\\", + L"C:\\a\\b\\", L"C:\\a\\", + L"C:\\a", L"C:\\", + L"C:\\", NULL, + L"\"C:\\a\\b\\c\"", L"\"C:\\a\\b\\", + L"\"C:\\a\\b\\\"c", L"\"C:\\a\\b\\", + L"\"C:\\a\\b\"\\c", L"\"C:\\a\\b\"\\", + L"\"C:\\a\\\"b\\c", L"\"C:\\a\\\"b\\", + L"C:\\a\"\\\"b\\c", L"C:\\a\"\\\"b\\", + L"C:\\a\"\\b\\c\"", L"C:\\a\"\\b\\", + }; + + try + { + for (DWORD i = 0; i < countof(rgwzPaths); i += 2) + { + hr = PathGetParentPath(rgwzPaths[i], &sczPath, NULL); + NativeAssert::Succeeded(hr, "PathGetParentPath: {0}", rgwzPaths[i]); + NativeAssert::StringEqual(rgwzPaths[i + 1], sczPath); + } + } + finally + { + ReleaseStr(sczPath); + } + } + [Fact] void PathGetFullPathNameTest() { @@ -693,6 +736,12 @@ namespace DutilTests NativeAssert::StringEqual(L"Software\\Microsoft\\Windows\\", rgsczPaths[2]); ReleaseNullStrArray(rgsczPaths, cPaths); + hr = PathGetHierarchyArray(L"Software", &rgsczPaths, &cPaths); + NativeAssert::Succeeded(hr, "Failed to get parent directories array for relative path"); + Assert::Equal(1, cPaths); + NativeAssert::StringEqual(L"Software", rgsczPaths[0]); + ReleaseNullStrArray(rgsczPaths, cPaths); + hr = PathGetHierarchyArray(L"c:/foo/bar/bas/a.txt", &rgsczPaths, &cPaths); NativeAssert::Succeeded(hr, "Failed to get parent directories array for regular file path"); Assert::Equal(5, cPaths); @@ -832,8 +881,12 @@ namespace DutilTests hr = StrAllocString(&sczPath, rgwzPaths[i], 0); NativeAssert::Succeeded(hr, "Failed to copy string"); - hr = PathPrefix(&sczPath); + hr = PathPrefix(&sczPath, 0, 0); NativeAssert::Succeeded(hr, "PathPrefix: {0}", rgwzPaths[i]); + NativeAssert::StringEqual(rgwzPaths[i], sczPath); + + hr = PathPrefix(&sczPath, 0, PATH_PREFIX_SHORT_PATHS); + NativeAssert::Succeeded(hr, "PathPrefix (SHORT_PATHS): {0}", rgwzPaths[i]); NativeAssert::StringEqual(rgwzPaths[i + 1], sczPath); } } @@ -871,7 +924,7 @@ namespace DutilTests hr = StrAllocString(&sczPath, rgwzPaths[i], 0); NativeAssert::Succeeded(hr, "Failed to copy string"); - hr = PathPrefix(&sczPath); + hr = PathPrefix(&sczPath, 0, PATH_PREFIX_EXPECT_FULLY_QUALIFIED); NativeAssert::SpecificReturnCode(E_INVALIDARG, hr, "PathPrefix: {0}, {1}", rgwzPaths[i], sczPath); } } @@ -884,187 +937,152 @@ namespace DutilTests [Fact] void PathIsRootedAndFullyQualifiedTest() { - HRESULT hr = S_OK; - LPWSTR sczPath = NULL; - LPCWSTR rgwzPaths[15] = - { - L"//", - L"///", - L"C:/", - L"C://", - L"C:/foo1", - L"C://foo2", - L"//test/unc/path/to/something", - L"//a/b/c/d/e", - L"//a/b/", - L"//a/b", - L"//test/unc", - L"//Server", - L"//Server/Foo.txt", - L"//Server/Share/Foo.txt", - L"//Server/Share/Test/Foo.txt", + LPCWSTR rgwzPaths[30] = + { + L"//", L"", + L"///", L"", + L"C:/", L"", + L"C://", L"/", + L"C:/foo1", L"foo1", + L"C://foo2", L"/foo2", + L"//test/unc/path/to/something", L"path/to/something", + L"//a/b/c/d/e", L"c/d/e", + L"//a/b/", L"", + L"//a/b", L"", + L"//test/unc", L"", + L"//Server", L"", + L"//Server/Foo.txt", L"", + L"//Server/Share/Foo.txt", L"Foo.txt", + L"//Server/Share/Test/Foo.txt", L"Test/Foo.txt", }; - try - { - for (DWORD i = 0; i < countof(rgwzPaths); ++i) - { - ValidateFullyQualifiedPath(rgwzPaths[i], TRUE, FALSE); - ValidateRootedPath(rgwzPaths[i], TRUE); - - hr = StrAllocString(&sczPath, rgwzPaths[i], 0); - NativeAssert::Succeeded(hr, "Failed to copy string"); - - PathFixedReplaceForwardSlashes(sczPath); - ValidateFullyQualifiedPath(sczPath, TRUE, FALSE); - ValidateRootedPath(sczPath, TRUE); - } - } - finally - { - ReleaseStr(sczPath); - } + ValidateSkipPastRoot(rgwzPaths, countof(rgwzPaths), FALSE, TRUE, TRUE); } [Fact] void PathIsRootedAndFullyQualifiedWithPrefixTest() { - HRESULT hr = S_OK; - LPWSTR sczPath = NULL; - LPCWSTR rgwzPaths[6] = + LPCWSTR rgwzPaths[12] = { - L"//?/UNC/test/unc/path/to/something", - L"//?/UNC/test/unc", - L"//?/UNC/a/b1", - L"//?/UNC/a/b2/", - L"//?/C:/foo/bar.txt", - L"/??/C:/foo/bar.txt", + L"//?/UNC/test/unc/path/to/something", L"path/to/something", + L"//?/UNC/test/unc", L"", + L"//?/UNC/a/b1", L"", + L"//?/UNC/a/b2/", L"", + L"//?/C:/foo/bar.txt", L"foo/bar.txt", + L"/??/C:/foo/bar.txt", L"foo/bar.txt", }; - try - { - for (DWORD i = 0; i < countof(rgwzPaths); ++i) - { - ValidateFullyQualifiedPath(rgwzPaths[i], TRUE, TRUE); - ValidateRootedPath(rgwzPaths[i], TRUE); - - hr = StrAllocString(&sczPath, rgwzPaths[i], 0); - NativeAssert::Succeeded(hr, "Failed to copy string"); - - PathFixedReplaceForwardSlashes(sczPath); - ValidateFullyQualifiedPath(sczPath, TRUE, TRUE); - ValidateRootedPath(sczPath, TRUE); - } - } - finally - { - ReleaseStr(sczPath); - } + ValidateSkipPastRoot(rgwzPaths, countof(rgwzPaths), TRUE, TRUE, TRUE); } [Fact] void PathIsRootedButNotFullyQualifiedTest() { - HRESULT hr = S_OK; - LPWSTR sczPath = NULL; - LPCWSTR rgwzPaths[7] = + LPCWSTR rgwzPaths[14] = { - L"/", - L"a:", - L"A:", - L"z:", - L"Z:", - L"C:foo.txt", - L"/dir", + L"/", L"", + L"a:", L"", + L"A:", L"", + L"z:", L"", + L"Z:", L"", + L"C:foo.txt", L"foo.txt", + L"/dir", L"dir", }; - try - { - for (DWORD i = 0; i < countof(rgwzPaths); ++i) - { - ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE); - ValidateRootedPath(rgwzPaths[i], TRUE); - - hr = StrAllocString(&sczPath, rgwzPaths[i], 0); - NativeAssert::Succeeded(hr, "Failed to copy string"); - - PathFixedReplaceForwardSlashes(sczPath); - ValidateFullyQualifiedPath(sczPath, FALSE, FALSE); - ValidateRootedPath(sczPath, TRUE); - } - } - finally - { - ReleaseStr(sczPath); - } + ValidateSkipPastRoot(rgwzPaths, countof(rgwzPaths), FALSE, FALSE, TRUE); } [Fact] void PathIsNotRootedAndNotFullyQualifiedTest() { - HRESULT hr = S_OK; - LPWSTR sczPath = NULL; - LPCWSTR rgwzPaths[9] = + LPCWSTR rgwzPaths[18] = { - NULL, - L"", - L"dir", - L"dir/subdir", - L"@:/foo", // 064 = @ 065 = A - L"[://", // 091 = [ 090 = Z - L"`:/foo ", // 096 = ` 097 = a - L"{://", // 123 = { 122 = z - L"[:", + NULL, NULL, + L"", NULL, + L"dir", NULL, + L"dir/subdir", NULL, + L"@:/foo", NULL, // 064 = @ 065 = A + L"[://", NULL, // 091 = [ 090 = Z + L"`:/foo ", NULL, // 096 = ` 097 = a + L"{://", NULL, // 123 = { 122 = z + L"[:", NULL, }; + ValidateSkipPastRoot(rgwzPaths, countof(rgwzPaths), FALSE, FALSE, FALSE); + } + + void ValidateSkipPastRoot(LPCWSTR* rgwzPaths, DWORD cPaths, BOOL fExpectedPrefix, BOOL fExpectedFullyQualified, BOOL fExpectedRooted) + { + HRESULT hr = S_OK; + LPWSTR sczPath = NULL; + LPWSTR sczSkipRootPath = NULL; + LPCWSTR wzSkipRootPath = NULL; + BOOL fHasPrefix = FALSE; + try { - for (DWORD i = 0; i < countof(rgwzPaths); ++i) + for (DWORD i = 0; i < cPaths; i += 2) { - ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE); - ValidateRootedPath(rgwzPaths[i], FALSE); + wzSkipRootPath = PathSkipPastRoot(rgwzPaths[i], &fHasPrefix, NULL, NULL); + NativeAssert::StringEqual(rgwzPaths[i + 1], wzSkipRootPath); + ValidateExtendedPrefixPath(rgwzPaths[i], fExpectedPrefix, fHasPrefix); + ValidateFullyQualifiedPath(rgwzPaths[i], fExpectedFullyQualified); + ValidateRootedPath(rgwzPaths[i], fExpectedRooted); - if (!rgwzPaths[i]) + if (rgwzPaths[i]) { - continue; + hr = StrAllocString(&sczPath, rgwzPaths[i], 0); + NativeAssert::Succeeded(hr, "Failed to copy string"); + + PathFixedReplaceForwardSlashes(sczPath); } - hr = StrAllocString(&sczPath, rgwzPaths[i], 0); - NativeAssert::Succeeded(hr, "Failed to copy string"); + if (rgwzPaths[i + 1]) + { + hr = StrAllocString(&sczSkipRootPath, rgwzPaths[i + 1], 0); + NativeAssert::Succeeded(hr, "Failed to copy string"); - PathFixedReplaceForwardSlashes(sczPath); - ValidateFullyQualifiedPath(sczPath, FALSE, FALSE); - ValidateRootedPath(sczPath, FALSE); + PathFixedReplaceForwardSlashes(sczSkipRootPath); + } + + wzSkipRootPath = PathSkipPastRoot(sczPath, &fHasPrefix, NULL, NULL); + NativeAssert::StringEqual(sczSkipRootPath, wzSkipRootPath); + ValidateExtendedPrefixPath(sczPath, fExpectedPrefix, fHasPrefix); + ValidateFullyQualifiedPath(sczPath, fExpectedFullyQualified); + ValidateRootedPath(sczPath, fExpectedRooted); } } finally { ReleaseStr(sczPath); + ReleaseStr(sczSkipRootPath); } } - void ValidateFullyQualifiedPath(LPCWSTR wzPath, BOOL fExpected, BOOL fExpectedHasPrefix) + void ValidateExtendedPrefixPath(LPCWSTR wzPath, BOOL fExpected, BOOL fHasExtendedPrefix) { - BOOL fHasLongPathPrefix = FALSE; - BOOL fRooted = PathIsFullyQualified(wzPath, &fHasLongPathPrefix); - String^ message = String::Format("IsFullyQualified: {0}", gcnew String(wzPath)); + String^ message = String::Format("HasExtendedPrefix: {0}", gcnew String(wzPath)); if (fExpected) { - Assert::True(fRooted, message); + Assert::True(fHasExtendedPrefix, message); } else { - Assert::False(fRooted, message); + Assert::False(fHasExtendedPrefix, message); } + } - message = String::Format("HasLongPathPrefix: {0}", gcnew String(wzPath)); - if (fExpectedHasPrefix) + void ValidateFullyQualifiedPath(LPCWSTR wzPath, BOOL fExpected) + { + BOOL fRooted = PathIsFullyQualified(wzPath); + String^ message = String::Format("IsFullyQualified: {0}", gcnew String(wzPath)); + if (fExpected) { - Assert::True(fHasLongPathPrefix, message); + Assert::True(fRooted, message); } else { - Assert::False(fHasLongPathPrefix, message); + Assert::False(fRooted, message); } } -- cgit v1.2.3-55-g6feb