From dea25f58e6119ef1acc5c5cc2a7c98e52cdff519 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Thu, 26 May 2022 17:33:41 -0500 Subject: Add PathCanonicalizeForComparison. --- src/burn/engine/variable.cpp | 2 +- src/libs/dutil/WixToolset.DUtil/inc/pathutil.h | 56 ++- src/libs/dutil/WixToolset.DUtil/path2utl.cpp | 149 ++++-- src/libs/dutil/WixToolset.DUtil/pathutil.cpp | 171 ++++++- .../dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj | 2 +- src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp | 554 +++++++++++++++++++-- .../PayloadFixture.cs | 2 +- .../TestData/Payload/CanonicalizeName.wxs | 2 +- 8 files changed, 822 insertions(+), 116 deletions(-) (limited to 'src') diff --git a/src/burn/engine/variable.cpp b/src/burn/engine/variable.cpp index 718751e5..c96e9d95 100644 --- a/src/burn/engine/variable.cpp +++ b/src/burn/engine/variable.cpp @@ -1969,7 +1969,7 @@ static HRESULT InitializeVariableSystemFolder( { HRESULT hr = S_OK; BOOL f64 = (BOOL)dwpData; - WCHAR wzSystemFolder[MAX_PATH] = { }; + WCHAR wzSystemFolder[MAX_PATH + 2] = { }; #if !defined(_WIN64) BOOL fIsWow64 = FALSE; diff --git a/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h b/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h index 602b4a80..fc6bb3bb 100644 --- a/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h +++ b/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h @@ -6,7 +6,17 @@ extern "C" { #endif -typedef enum PATH_EXPAND +typedef enum _PATH_CANONICALIZE +{ + // Always prefix fully qualified paths with the long path prefix (\\?\). + PATH_CANONICALIZE_APPEND_LONG_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. + PATH_CANONICALIZE_KEEP_UNC_ROOT = 0x0004, +} PATH_CANONICALIZE; + +typedef enum _PATH_EXPAND { PATH_EXPAND_ENVIRONMENT = 0x0001, PATH_EXPAND_FULLPATH = 0x0002, @@ -29,7 +39,9 @@ DAPI_(LPCWSTR) PathExtension( ); /******************************************************************* - PathGetDirectory - extracts the directory from a path. + 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. ********************************************************************/ DAPI_(HRESULT) PathGetDirectory( __in_z LPCWSTR wzPath, @@ -38,6 +50,7 @@ DAPI_(HRESULT) PathGetDirectory( /******************************************************************* PathGetParentPath - extracts the parent directory from a full path. + *psczDirectory is NULL if the path only contains a file name. ********************************************************************/ DAPI_(HRESULT) PathGetParentPath( __in_z LPCWSTR wzPath, @@ -62,6 +75,21 @@ DAPI_(HRESULT) PathPrefix( __inout LPWSTR *psczFullPath ); +/******************************************************************* + PathFixedNormalizeSlashes - replaces all / with \ and + removes redundant consecutive slashes. +********************************************************************/ +DAPI_(HRESULT) PathFixedNormalizeSlashes( + __inout_z LPWSTR wzPath + ); + +/******************************************************************* + PathFixedReplaceForwardSlashes - replaces all / with \ +********************************************************************/ +DAPI_(void) PathFixedReplaceForwardSlashes( + __inout_z LPWSTR wzPath + ); + /******************************************************************* PathFixedBackslashTerminate - appends a \ if path does not have it already, but fails if the buffer is @@ -77,7 +105,7 @@ DAPI_(HRESULT) PathFixedBackslashTerminate( already. ********************************************************************/ DAPI_(HRESULT) PathBackslashTerminate( - __inout LPWSTR* psczPath + __inout_z LPWSTR* psczPath ); /******************************************************************* @@ -168,7 +196,7 @@ DAPI_(HRESULT) PathGetKnownFolder( /******************************************************************* PathIsFullyQualified - returns true if the path is fully qualified; false otherwise. Note that some rooted paths like C:dir are not fully qualified. - For example, these are all fully qualified: C:\dir, \\server\share, \\?\C:\dir. + For example, these are all fully qualified: C:\dir, C:/dir, \\server\share, \\?\C:\dir. For example, these are not fully qualified: C:dir, C:, \dir, dir, dir\subdir. *******************************************************************/ DAPI_(BOOL) PathIsFullyQualified( @@ -179,7 +207,7 @@ DAPI_(BOOL) PathIsFullyQualified( /******************************************************************* PathIsRooted - returns true if the path is rooted; false otherwise. Note that some rooted paths like C:dir are not fully qualified. - For example, these are all rooted: C:\dir, C:dir, C:, \dir, \\server\share, \\?\C:\dir. + For example, these are all rooted: C:\dir, C:/dir, C:dir, C:, \dir, \\server\share, \\?\C:\dir. For example, these are not rooted: dir, dir\subdir. *******************************************************************/ DAPI_(BOOL) PathIsRooted( @@ -240,7 +268,7 @@ DAPI_(HRESULT) PathGetHierarchyArray( ); /******************************************************************* - PathCanonicalizePath - wrapper around PathCanonicalizeW. + PathCanonicalizePath - wrapper around PathCanonicalizeW. *******************************************************************/ DAPI_(HRESULT) PathCanonicalizePath( __in_z LPCWSTR wzPath, @@ -248,8 +276,20 @@ DAPI_(HRESULT) PathCanonicalizePath( ); /******************************************************************* -PathDirectoryContainsPath - checks if wzPath is located inside - wzDirectory. + PathCanonicalizeForComparison - canonicalizes the path based on the given flags. + . and .. directories are collapsed. + All / are replaced with \. + All redundant consecutive slashes are replaced with a single \. +*******************************************************************/ +DAPI_(HRESULT) PathCanonicalizeForComparison( + __in_z LPCWSTR wzPath, + __in DWORD dwCanonicalizeFlags, + __deref_out_z LPWSTR* psczCanonicalized + ); + +/******************************************************************* + PathDirectoryContainsPath - checks if wzPath is located inside wzDirectory. + wzDirectory must be a fully qualified path. *******************************************************************/ DAPI_(HRESULT) PathDirectoryContainsPath( __in_z LPCWSTR wzDirectory, diff --git a/src/libs/dutil/WixToolset.DUtil/path2utl.cpp b/src/libs/dutil/WixToolset.DUtil/path2utl.cpp index ff3a946d..45157d0b 100644 --- a/src/libs/dutil/WixToolset.DUtil/path2utl.cpp +++ b/src/libs/dutil/WixToolset.DUtil/path2utl.cpp @@ -9,6 +9,7 @@ #define PathExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__) #define PathExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__) #define PathExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__) +#define PathExitWithRootFailure(x, e, s, ...) ExitWithRootFailureSource(DUTIL_SOURCE_PATHUTIL, x, e, s, __VA_ARGS__) #define PathExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__) #define PathExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_PATHUTIL, p, x, e, s, __VA_ARGS__) #define PathExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_PATHUTIL, p, x, s, __VA_ARGS__) @@ -42,63 +43,141 @@ LExit: return hr; } -DAPI_(HRESULT) PathDirectoryContainsPath( - __in_z LPCWSTR wzDirectory, - __in_z LPCWSTR wzPath +DAPI_(HRESULT) PathCanonicalizeForComparison( + __in_z LPCWSTR wzPath, + __in DWORD dwCanonicalizeFlags, + __deref_out_z LPWSTR* psczCanonicalized ) { HRESULT hr = S_OK; - LPWSTR sczPath = NULL; - LPWSTR sczDirectory = NULL; - LPWSTR sczOriginalPath = NULL; - LPWSTR sczOriginalDirectory = NULL; + LPWSTR sczNormalizedPath = NULL; + LPCWSTR wzNormalizedPath = NULL; + SIZE_T cchUncRootLength = 0; + BOOL fHasPrefix = FALSE; - hr = PathCanonicalizePath(wzPath, &sczOriginalPath); - PathExitOnFailure(hr, "Failed to canonicalize the path."); + hr = StrAllocString(&sczNormalizedPath, wzPath, 0); + PathExitOnFailure(hr, "Failed to allocate string for the normalized path."); - hr = PathCanonicalizePath(wzDirectory, &sczOriginalDirectory); - PathExitOnFailure(hr, "Failed to canonicalize the directory."); + PathFixedNormalizeSlashes(sczNormalizedPath); + + wzNormalizedPath = sczNormalizedPath; - if (!sczOriginalPath || !*sczOriginalPath) + if (PATH_CANONICALIZE_KEEP_UNC_ROOT & dwCanonicalizeFlags) { - ExitFunction1(hr = S_FALSE); + if (L'\\' == sczNormalizedPath[0] && (L'\\' == sczNormalizedPath[1] || L'?' == sczNormalizedPath[1]) && L'?' == sczNormalizedPath[2] && L'\\' == sczNormalizedPath[3]) + { + 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; + } + } + } + } + + if (*wzNormalizedPath) + { + hr = PathCanonicalizePath(wzNormalizedPath, psczCanonicalized); + PathExitOnFailure(hr, "Failed to canonicalize: %ls", wzNormalizedPath); } - if (!sczOriginalDirectory || !*sczOriginalDirectory) + else { - ExitFunction1(hr = S_FALSE); + Assert(cchUncRootLength); + ReleaseStr(*psczCanonicalized); + *psczCanonicalized = sczNormalizedPath; + sczNormalizedPath = NULL; + cchUncRootLength = 0; } - sczPath = sczOriginalPath; - sczDirectory = sczOriginalDirectory; + if (cchUncRootLength) + { + hr = StrAllocPrefix(psczCanonicalized, sczNormalizedPath, cchUncRootLength); + PathExitOnFailure(hr, "Failed to prefix the UNC root to the canonicalized path."); + } - for (; *sczDirectory;) + if (PATH_CANONICALIZE_BACKSLASH_TERMINATE & dwCanonicalizeFlags) { - if (!*sczPath) - { - ExitFunction1(hr = S_FALSE); - } + hr = PathBackslashTerminate(psczCanonicalized); + PathExitOnFailure(hr, "Failed to backslash terminate the canonicalized path"); + } - if (CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, sczDirectory, 1, sczPath, 1)) - { - ExitFunction1(hr = S_FALSE); - } + if ((PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX & dwCanonicalizeFlags) && + PathIsFullyQualified(*psczCanonicalized, &fHasPrefix) && !fHasPrefix) + { + hr = PathPrefix(psczCanonicalized); + PathExitOnFailure(hr, "Failed to ensure the long path prefix on the canonicalized path"); + } + +LExit: + ReleaseStr(sczNormalizedPath); - ++sczDirectory; - ++sczPath; + return hr; +} + +DAPI_(HRESULT) PathDirectoryContainsPath( + __in_z LPCWSTR wzDirectory, + __in_z LPCWSTR wzPath + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCanonicalizedDirectory = NULL; + LPWSTR sczCanonicalizedPath = NULL; + DWORD dwDefaultFlags = PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX | PATH_CANONICALIZE_KEEP_UNC_ROOT; + size_t cchDirectory = 0; + + if (!wzDirectory || !*wzDirectory) + { + PathExitWithRootFailure(hr, E_INVALIDARG, "wzDirectory is required."); } + if (!wzPath || !*wzPath) + { + PathExitWithRootFailure(hr, E_INVALIDARG, "wzPath is required."); + } + + hr = PathCanonicalizeForComparison(wzDirectory, dwDefaultFlags | PATH_CANONICALIZE_BACKSLASH_TERMINATE, &sczCanonicalizedDirectory); + PathExitOnFailure(hr, "Failed to canonicalize the directory."); - --sczDirectory; - if (('\\' == *sczDirectory && *sczPath) || '\\' == *sczPath) + hr = PathCanonicalizeForComparison(wzPath, dwDefaultFlags, &sczCanonicalizedPath); + PathExitOnFailure(hr, "Failed to canonicalize the path."); + + if (!PathIsFullyQualified(sczCanonicalizedDirectory, NULL)) { - hr = S_OK; + PathExitWithRootFailure(hr, E_INVALIDARG, "wzDirectory must be a fully qualified path."); } - else + if (!sczCanonicalizedPath || !*sczCanonicalizedPath) { - hr = S_FALSE; + ExitFunction1(hr = S_FALSE); } + hr = ::StringCchLengthW(sczCanonicalizedDirectory, STRSAFE_MAX_CCH, &cchDirectory); + PathExitOnFailure(hr, "Failed to get length of canonicalized directory."); + + if (CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, sczCanonicalizedDirectory, (DWORD)cchDirectory, sczCanonicalizedPath, (DWORD)cchDirectory)) + { + ExitFunction1(hr = S_FALSE); + } + + hr = sczCanonicalizedPath[cchDirectory] ? S_OK : S_FALSE; + LExit: - ReleaseStr(sczOriginalPath); - ReleaseStr(sczOriginalDirectory); + ReleaseStr(sczCanonicalizedPath); + ReleaseStr(sczCanonicalizedDirectory); return hr; } diff --git a/src/libs/dutil/WixToolset.DUtil/pathutil.cpp b/src/libs/dutil/WixToolset.DUtil/pathutil.cpp index 314eab85..99be003c 100644 --- a/src/libs/dutil/WixToolset.DUtil/pathutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/pathutil.cpp @@ -20,6 +20,9 @@ #define PATH_GOOD_ENOUGH 64 +static BOOL IsPathSeparatorChar( + __in WCHAR wc + ); static BOOL IsValidDriveChar( __in WCHAR wc ); @@ -41,7 +44,7 @@ DAPI_(LPWSTR) PathFile( // \ => Windows path // / => unix and URL path // : => relative path from mapped root - if (L'\\' == *wz || L'/' == *wz || (L':' == *wz && wz == wzPath + 1)) + if (IsPathSeparatorChar(*wz) || (L':' == *wz && wz == wzPath + 1)) { wzFile = wz + 1; } @@ -64,7 +67,7 @@ DAPI_(LPCWSTR) PathExtension( LPCWSTR wzExtension = NULL; for (LPCWSTR wz = wzPath; *wz; ++wz) { - if (L'\\' == *wz || L'/' == *wz || L':' == *wz) + if (IsPathSeparatorChar(*wz) || L':' == *wz) { wzExtension = NULL; } @@ -84,7 +87,8 @@ DAPI_(HRESULT) PathGetDirectory( ) { HRESULT hr = S_OK; - size_t cchDirectory = SIZE_T_MAX; + LPCWSTR wzRemaining = NULL; + SIZE_T cchDirectory = 0; for (LPCWSTR wz = wzPath; *wz; ++wz) { @@ -92,18 +96,20 @@ DAPI_(HRESULT) PathGetDirectory( // \ => Windows path // / => unix and URL path // : => relative path from mapped root - if (L'\\' == *wz || L'/' == *wz || (L':' == *wz && wz == wzPath + 1)) + if (IsPathSeparatorChar(*wz) || (L':' == *wz && wz == wzPath + 1)) { - cchDirectory = static_cast(wz - wzPath) + 1; + wzRemaining = wz; } } - if (SIZE_T_MAX == cchDirectory) + if (!wzRemaining) { // we were given just a file name, so there's no directory available - return S_FALSE; + ExitFunction1(hr = S_FALSE); } + cchDirectory = static_cast(wzRemaining - wzPath) + 1; + hr = StrAllocString(psczDirectory, wzPath, cchDirectory); PathExitOnFailure(hr, "Failed to copy directory."); @@ -122,7 +128,7 @@ DAPI_(HRESULT) PathGetParentPath( for (LPCWSTR wz = wzPath; *wz; ++wz) { - if (wz[1] && (L'\\' == *wz || L'/' == *wz)) + if (IsPathSeparatorChar(*wz) && wz[1]) { wzParent = wz; } @@ -291,12 +297,13 @@ DAPI_(HRESULT) PathPrefix( hr = StrAllocPrefix(psczFullPath, L"\\\\?\\", 4); PathExitOnFailure(hr, "Failed to add prefix to file path."); } - else if (fFullyQualified && L'\\' == wzFullPath[1]) // UNC + else if (fFullyQualified && IsPathSeparatorChar(wzFullPath[1])) // UNC { hr = StrSize(*psczFullPath, &cbFullPath); PathExitOnFailure(hr, "Failed to get size of full path."); memmove_s(wzFullPath, cbFullPath, wzFullPath + 1, cbFullPath - sizeof(WCHAR)); + wzFullPath[0] = L'\\'; hr = StrAllocPrefix(psczFullPath, L"\\\\?\\UNC", 7); PathExitOnFailure(hr, "Failed to add prefix to UNC path."); @@ -312,6 +319,90 @@ LExit: } +DAPI_(HRESULT) PathFixedNormalizeSlashes( + __inout_z LPWSTR wzPath + ) +{ + HRESULT hr = S_OK; + size_t cchLength = 0; + BOOL fAllowDoubleSlash = FALSE; + SIZE_T iSource = 0; + SIZE_T jDestination = 0; + + hr = ::StringCchLengthW(wzPath, STRSAFE_MAX_CCH, &cchLength); + PathExitOnFailure(hr, "Failed to get length of path."); + + if (1 < cchLength && IsPathSeparatorChar(wzPath[0])) + { + if (IsPathSeparatorChar(wzPath[1])) + { + // \\?\\a\ is not equivalent to \\?\a\ and \\server\\a\ is not equivalent to \\server\a\. + fAllowDoubleSlash = TRUE; + wzPath[0] = '\\'; + wzPath[1] = '\\'; + iSource = 2; + jDestination = 2; + } + else if (2 < cchLength && L'?' == wzPath[1] && L'?' == wzPath[2]) + { + // \??\\a\ is not equivalent to \??\a\. + fAllowDoubleSlash = TRUE; + wzPath[0] = '\\'; + wzPath[1] = '?'; + wzPath[2] = '?'; + iSource = 3; + jDestination = 3; + } + } + + for (; iSource < cchLength; ++iSource) + { + if (IsPathSeparatorChar(wzPath[iSource])) + { + if (fAllowDoubleSlash) + { + fAllowDoubleSlash = FALSE; + } + else if (IsPathSeparatorChar(wzPath[iSource + 1])) + { + // Skip consecutive slashes. + continue; + } + + wzPath[jDestination] = '\\'; + } + else + { + wzPath[jDestination] = wzPath[iSource]; + } + + ++jDestination; + } + + for (; jDestination < cchLength; ++jDestination) + { + wzPath[jDestination] = '\0'; + } + +LExit: + return hr; +} + + +DAPI_(void) PathFixedReplaceForwardSlashes( + __inout_z LPWSTR wzPath + ) +{ + for (LPWSTR wz = wzPath; *wz; ++wz) + { + if (L'/' == *wz) + { + *wz = L'\\'; + } + } +} + + DAPI_(HRESULT) PathFixedBackslashTerminate( __inout_ecount_z(cchPath) LPWSTR wzPath, __in SIZE_T cchPath @@ -320,17 +411,28 @@ DAPI_(HRESULT) PathFixedBackslashTerminate( HRESULT hr = S_OK; size_t cchLength = 0; + if (!cchPath) + { + ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)); + } + hr = ::StringCchLengthW(wzPath, cchPath, &cchLength); PathExitOnFailure(hr, "Failed to get length of path."); - if (cchLength >= cchPath) + LPWSTR wzLast = wzPath + (cchLength - 1); + if (cchLength && L'/' == wzLast[0]) { - hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + wzLast[0] = L'\\'; } - else if (L'\\' != wzPath[cchLength - 1]) + else if (!cchLength || L'\\' != wzLast[0]) { - wzPath[cchLength] = L'\\'; - wzPath[cchLength + 1] = L'\0'; + if (cchLength + 2 > cchPath) + { + ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)); + } + + wzLast[1] = L'\\'; + wzLast[2] = L'\0'; } LExit: @@ -339,10 +441,10 @@ LExit: DAPI_(HRESULT) PathBackslashTerminate( - __inout LPWSTR* psczPath + __inout_z LPWSTR* psczPath ) { - Assert(psczPath && *psczPath); + Assert(psczPath); HRESULT hr = S_OK; SIZE_T cchPath = 0; @@ -354,7 +456,12 @@ DAPI_(HRESULT) PathBackslashTerminate( hr = ::StringCchLengthW(*psczPath, cchPath, &cchLength); PathExitOnFailure(hr, "Failed to get length of path."); - if (L'\\' != (*psczPath)[cchLength - 1]) + LPWSTR wzLast = *psczPath + (cchLength - 1); + if (cchLength && L'/' == wzLast[0]) + { + wzLast[0] = L'\\'; + } + else if (!cchLength || L'\\' != wzLast[0]) { hr = StrAllocConcat(psczPath, L"\\", 1); PathExitOnFailure(hr, "Failed to concat backslash onto string."); @@ -833,13 +940,13 @@ DAPI_(BOOL) PathIsFullyQualified( ExitFunction(); } - if (L'\\' != 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] && - L'\\' == wzPath[2]) + IsPathSeparatorChar(wzPath[2])) { fFullyQualified = TRUE; } @@ -849,14 +956,14 @@ DAPI_(BOOL) PathIsFullyQualified( // Non-drive fully qualified paths must start with \\ or \?. // \??\ is an archaic form of \\?\. - if (L'?' != wzPath[1] && L'\\' != wzPath[1]) + if (L'?' != wzPath[1] && !IsPathSeparatorChar(wzPath[1])) { ExitFunction(); } fFullyQualified = TRUE; - if (L'?' == wzPath[2] && L'\\' == wzPath[3]) + if (L'?' == wzPath[2] && IsPathSeparatorChar(wzPath[3])) { fHasLongPathPrefix = TRUE; } @@ -877,7 +984,7 @@ DAPI_(BOOL) PathIsRooted( ) { return wzPath && - (wzPath[0] == L'\\' || + (IsPathSeparatorChar(wzPath[0]) || IsValidDriveChar(wzPath[0]) && wzPath[1] == L':'); } @@ -1008,20 +1115,25 @@ DAPI_(HRESULT) PathGetHierarchyArray( for (size_t i = 0; i < cchPath; ++i) { - if (wzPath[i] == L'\\') + if (IsPathSeparatorChar(wzPath[i])) { ++cArraySpacesNeeded; } } - if (wzPath[cchPath - 1] != L'\\') + if (!IsPathSeparatorChar(wzPath[cchPath - 1])) { ++cArraySpacesNeeded; } // 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 (wzPath[0] == L'\\' && wzPath[1] == L'\\') + if (IsPathSeparatorChar(wzPath[0]) && IsPathSeparatorChar(wzPath[1])) { + if (3 > cArraySpacesNeeded) + { + ExitFunction1(hr = E_INVALIDARG); + } + cArraySpacesNeeded -= 3; } @@ -1042,7 +1154,7 @@ DAPI_(HRESULT) PathGetHierarchyArray( 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 (wzPath[cchPathCopy - 1] == L'\\') + if (IsPathSeparatorChar(wzPath[cchPathCopy - 1])) { sczPathCopy[cchPathCopy - 1] = L'\0'; } @@ -1063,6 +1175,13 @@ LExit: return hr; } +static BOOL IsPathSeparatorChar( + __in WCHAR wc + ) +{ + return L'/' == wc || L'\\' == wc; +} + static BOOL IsValidDriveChar( __in WCHAR wc ) diff --git a/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj index 1c821a7c..c37bdad1 100644 --- a/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj +++ b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj @@ -40,7 +40,7 @@ ..\..\WixToolset.DUtil\inc - rpcrt4.lib;Mpr.lib;Ws2_32.lib;urlmon.lib;wininet.lib + rpcrt4.lib;Mpr.lib;Ws2_32.lib;shlwapi.lib;urlmon.lib;wininet.lib diff --git a/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp index 65856514..04d0b447 100644 --- a/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp +++ b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp @@ -11,6 +11,290 @@ namespace DutilTests public ref class PathUtil { public: + [Fact] + void PathBackslashFixedTerminateTest() + { + HRESULT hr = S_OK; + WCHAR wzEmpty[1] = { L'\0' }; + WCHAR wzSingleLetter[1] = { L'a' }; + WCHAR wzSingleBackslash[1] = { L'\\' }; + WCHAR wzSingleForwardSlash[1] = { L'/' }; + WCHAR wzSingleLetterNullTerminated[2] = { L'a', L'\0' }; + WCHAR wzSingleBackslashNullTerminated[2] = { L'\\', L'\0' }; + WCHAR wzSingleForwardSlashNullTerminated[2] = { L'/', L'\0' }; + WCHAR wzExtraSpaceLetterNullTerminated[3] = { L'a', L'\0', L'\0' }; + WCHAR wzExtraSpaceBackslashNullTerminated[3] = { L'\\', L'\0', L'\0' }; + WCHAR wzExtraSpaceForwardSlashNullTerminated[3] = { L'/', L'\0', L'\0' }; + + hr = PathFixedBackslashTerminate(wzEmpty, 0); + NativeAssert::SpecificReturnCode(E_INSUFFICIENT_BUFFER, hr, "PathFixedBackslashTerminate: zero-length, {0}", wzEmpty); + + hr = PathFixedBackslashTerminate(wzEmpty, countof(wzEmpty)); + NativeAssert::SpecificReturnCode(E_INSUFFICIENT_BUFFER, hr, "PathFixedBackslashTerminate: '' (length 1), {0}", wzEmpty); + + hr = PathFixedBackslashTerminate(wzSingleLetter, countof(wzSingleLetter)); + NativeAssert::SpecificReturnCode(E_INVALIDARG, hr, "PathFixedBackslashTerminate: 'a' (length 1)"); + + hr = PathFixedBackslashTerminate(wzSingleBackslash, countof(wzSingleBackslash)); + NativeAssert::SpecificReturnCode(E_INVALIDARG, hr, "PathFixedBackslashTerminate: '\\' (length 1)"); + + hr = PathFixedBackslashTerminate(wzSingleForwardSlash, countof(wzSingleForwardSlash)); + NativeAssert::SpecificReturnCode(E_INVALIDARG, hr, "PathFixedBackslashTerminate: '/' (length 1)"); + + hr = PathFixedBackslashTerminate(wzSingleLetterNullTerminated, countof(wzSingleLetterNullTerminated)); + NativeAssert::SpecificReturnCode(E_INSUFFICIENT_BUFFER, hr, "PathFixedBackslashTerminate: 'a' (length 2)"); + + hr = PathFixedBackslashTerminate(wzSingleBackslashNullTerminated, countof(wzSingleBackslashNullTerminated)); + NativeAssert::Succeeded(hr, "PathFixedBackslashTerminate: '\\' (length 2)"); + NativeAssert::StringEqual(L"\\", wzSingleBackslashNullTerminated); + + hr = PathFixedBackslashTerminate(wzSingleForwardSlashNullTerminated, countof(wzSingleForwardSlashNullTerminated)); + NativeAssert::Succeeded(hr, "PathFixedBackslashTerminate: '/' (length 2)"); + NativeAssert::StringEqual(L"\\", wzSingleForwardSlashNullTerminated); + + hr = PathFixedBackslashTerminate(wzExtraSpaceLetterNullTerminated, countof(wzExtraSpaceLetterNullTerminated)); + NativeAssert::Succeeded(hr, "PathFixedBackslashTerminate: 'a' (length 3)"); + NativeAssert::StringEqual(L"a\\", wzExtraSpaceLetterNullTerminated); + + hr = PathFixedBackslashTerminate(wzExtraSpaceBackslashNullTerminated, countof(wzExtraSpaceBackslashNullTerminated)); + NativeAssert::Succeeded(hr, "PathFixedBackslashTerminate: '\\' (length 3)"); + NativeAssert::StringEqual(L"\\", wzExtraSpaceBackslashNullTerminated); + + hr = PathFixedBackslashTerminate(wzExtraSpaceForwardSlashNullTerminated, countof(wzExtraSpaceForwardSlashNullTerminated)); + NativeAssert::Succeeded(hr, "PathFixedBackslashTerminate: '/' (length 3)"); + NativeAssert::StringEqual(L"\\", wzExtraSpaceForwardSlashNullTerminated); + } + + [Fact] + void PathBackslashTerminateTest() + { + HRESULT hr = S_OK; + LPWSTR sczPath = NULL; + LPCWSTR rgwzPaths[16] = + { + L"", L"\\", + L"a", L"a\\", + L"\\", L"\\", + L"a\\", L"a\\", + L"/", L"\\", + L"a/", L"a\\", + L"\\\\", L"\\\\", + L"//", L"/\\", + }; + + try + { + for (DWORD i = 0; i < countof(rgwzPaths); i += 2) + { + hr = StrAllocString(&sczPath, rgwzPaths[i], 0); + NativeAssert::Succeeded(hr, "Failed to copy string"); + + hr = PathBackslashTerminate(&sczPath); + NativeAssert::Succeeded(hr, "PathBackslashTerminate: {0}", rgwzPaths[i]); + NativeAssert::StringEqual(rgwzPaths[i + 1], sczPath); + } + } + finally + { + ReleaseStr(sczPath); + } + } + + [Fact] + void PathCanonicalizeForComparisonTest() + { + HRESULT hr = S_OK; + LPWSTR sczCanonicalized = NULL; + + try + { + hr = PathCanonicalizeForComparison(L"C:\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789", 0, &sczCanonicalized); + Assert::Equal(HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE), hr); + + hr = PathCanonicalizeForComparison(L"\\\\?\\C:\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789", 0, &sczCanonicalized); + Assert::Equal(HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE), hr); + + hr = PathCanonicalizeForComparison(L"\\\\server", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\server", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"\\\\server", 0, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\server", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"\\\\server\\", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\server\\", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"\\\\server\\share", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\server\\share", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"\\\\server\\share\\", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized); + 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); + 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"\\\\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); + + hr = PathCanonicalizeForComparison(L"\\\\server\\share\\..\\..\\otherdir\\unc.exe", 0, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\otherdir\\unc.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"\\\\?\\UNC\\server\\share\\..\\..\\otherdir\\unc.exe", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\?\\UNC\\server\\share\\otherdir\\unc.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"\\\\?\\UNC\\server\\share\\..\\..\\otherdir\\unc.exe", 0, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\otherdir\\unc.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"C:\\dir\\subdir\\..\\..\\..\\otherdir\\pastroot.exe", 0, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"C:\\otherdir\\pastroot.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"\\\\?\\C:\\dir\\subdir\\..\\..\\..\\otherdir\\pastroot.exe", 0, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"C:\\otherdir\\pastroot.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"\\\\?\\C:dir\\subdir\\..\\..\\..\\otherdir\\pastroot.exe", 0, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\otherdir\\pastroot.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"C:dir\\subdir\\..\\..\\..\\otherdir\\pastrelativeroot.exe", 0, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\otherdir\\pastrelativeroot.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"A:dir\\subdir\\..\\..\\otherdir\\relativeroot.exe", 0, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\otherdir\\relativeroot.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"C:dir\\subdir\\otherdir\\relativeroot.exe", 0, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"C:dir\\subdir\\otherdir\\relativeroot.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"C:\\dir\\subdir\\..\\..\\otherdir\\backslashes.exe", 0, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"C:\\otherdir\\backslashes.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"C:\\dir\\subdir\\..\\..\\otherdir\\\\consecutivebackslashes.exe", 0, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"C:\\otherdir\\consecutivebackslashes.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"C:/dir/subdir/../../otherdir/forwardslashes.exe", 0, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"C:\\otherdir\\forwardslashes.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"\\\\?\\C:\\test\\..\\validlongpath.exe", 0, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"C:\\validlongpath.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"\\\\?\\test\\..\\invalidlongpath.exe", 0, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\?\\invalidlongpath.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"C:\\.\\invalid:pathchars?.exe", 0, &sczCanonicalized); + 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); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\?\\C:\\addprefix.exe", sczCanonicalized); + + hr = PathCanonicalizeForComparison(L"C:\\addbackslash.exe", PATH_CANONICALIZE_BACKSLASH_TERMINATE, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"C:\\addbackslash.exe\\", sczCanonicalized); + } + finally + { + ReleaseStr(sczCanonicalized); + } + } + + [Fact] + void PathDirectoryContainsPathTest() + { + HRESULT hr = S_OK; + + hr = PathDirectoryContainsPath(L"", L""); + Assert::Equal(E_INVALIDARG, hr); + + hr = PathDirectoryContainsPath(L"C:\\Directory", L""); + Assert::Equal(E_INVALIDARG, hr); + + hr = PathDirectoryContainsPath(L"", L"C:\\Directory"); + Assert::Equal(E_INVALIDARG, hr); + + hr = PathDirectoryContainsPath(L"C:\\Directory", L"C:\\Directory"); + Assert::Equal(S_FALSE, hr); + + hr = PathDirectoryContainsPath(L"C:\\Dir", L"C:\\Directory"); + Assert::Equal(S_FALSE, hr); + + hr = PathDirectoryContainsPath(L"C:\\Directory", L"C:\\"); + Assert::Equal(S_FALSE, hr); + + hr = PathDirectoryContainsPath(L"C:\\Directory", L"C:\\DirectoryPlus"); + Assert::Equal(S_FALSE, hr); + + hr = PathDirectoryContainsPath(L"C:\\Directory\\", L"C:\\DirectoryPlus"); + Assert::Equal(S_FALSE, hr); + + hr = PathDirectoryContainsPath(L"C:\\Directory\\", L"C:\\Directory\\../Plus"); + Assert::Equal(S_FALSE, hr); + + hr = PathDirectoryContainsPath(L"C:\\Directory\\", L"C:\\Directory/../Plus"); + Assert::Equal(S_FALSE, hr); + + hr = PathDirectoryContainsPath(L"\\\\server\\share\\Directory", L"\\\\server\\share\\DirectoryPlus"); + Assert::Equal(S_FALSE, hr); + + hr = PathDirectoryContainsPath(L"\\\\server\\share\\Directory", L"\\\\discarded\\..\\server\\share\\Directory\\Plus"); + Assert::Equal(S_FALSE, hr); + + hr = PathDirectoryContainsPath(L"..\\..", L"..\\..\\plus"); + Assert::Equal(E_INVALIDARG, hr); + + hr = PathDirectoryContainsPath(L"..\\..", L"\\..\\..\\plus"); + Assert::Equal(E_INVALIDARG, hr); + + hr = PathDirectoryContainsPath(L"\\..\\..", L"\\..\\..\\plus"); + Assert::Equal(E_INVALIDARG, hr); + + hr = PathDirectoryContainsPath(L"C:..\\..", L"C:..\\..\\plus"); + Assert::Equal(E_INVALIDARG, hr); + + hr = PathDirectoryContainsPath(L"\\\\server\\share\\Directory", L"\\\\server\\share\\Directory\\Plus"); + Assert::Equal(S_OK, hr); + + hr = PathDirectoryContainsPath(L"C:\\Directory", L"C:\\directory\\plus"); + Assert::Equal(S_OK, hr); + + hr = PathDirectoryContainsPath(L"C:\\Directory\\", L"C:\\Directory\\Plus"); + Assert::Equal(S_OK, hr); + + hr = PathDirectoryContainsPath(L"C:\\Directory", L"C:\\.\\Directory\\Plus"); + Assert::Equal(S_OK, hr); + + hr = PathDirectoryContainsPath(L"C:\\Directory", L"C:\\Directory/Plus"); + Assert::Equal(S_OK, hr); + + hr = PathDirectoryContainsPath(L"C:\\Directory\\", L"C:\\Directory/Plus"); + Assert::Equal(S_OK, hr); + + hr = PathDirectoryContainsPath(L"\\\\?\\C:\\Directory", L"C:\\Directory\\Plus"); + Assert::Equal(S_OK, hr); + } + [Fact] void PathGetDirectoryTest() { @@ -103,6 +387,57 @@ namespace DutilTests NativeAssert::StringEqual(L"Software\\Microsoft\\", rgsczPaths[1]); NativeAssert::StringEqual(L"Software\\Microsoft\\Windows\\", rgsczPaths[2]); 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); + NativeAssert::StringEqual(L"c:/", rgsczPaths[0]); + NativeAssert::StringEqual(L"c:/foo/", rgsczPaths[1]); + NativeAssert::StringEqual(L"c:/foo/bar/", rgsczPaths[2]); + NativeAssert::StringEqual(L"c:/foo/bar/bas/", rgsczPaths[3]); + NativeAssert::StringEqual(L"c:/foo/bar/bas/a.txt", rgsczPaths[4]); + ReleaseNullStrArray(rgsczPaths, cPaths); + + hr = PathGetHierarchyArray(L"c:/foo/bar/bas/", &rgsczPaths, &cPaths); + NativeAssert::Succeeded(hr, "Failed to get parent directories array for regular directory path"); + Assert::Equal(4, cPaths); + NativeAssert::StringEqual(L"c:/", rgsczPaths[0]); + NativeAssert::StringEqual(L"c:/foo/", rgsczPaths[1]); + NativeAssert::StringEqual(L"c:/foo/bar/", rgsczPaths[2]); + NativeAssert::StringEqual(L"c:/foo/bar/bas/", rgsczPaths[3]); + ReleaseNullStrArray(rgsczPaths, cPaths); + + hr = PathGetHierarchyArray(L"//server/share/subdir/file.txt", &rgsczPaths, &cPaths); + NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC file path"); + Assert::Equal(3, cPaths); + NativeAssert::StringEqual(L"//server/share/", rgsczPaths[0]); + NativeAssert::StringEqual(L"//server/share/subdir/", rgsczPaths[1]); + NativeAssert::StringEqual(L"//server/share/subdir/file.txt", rgsczPaths[2]); + ReleaseNullStrArray(rgsczPaths, cPaths); + + hr = PathGetHierarchyArray(L"//server/share/subdir/", &rgsczPaths, &cPaths); + NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC directory path"); + Assert::Equal(2, cPaths); + NativeAssert::StringEqual(L"//server/share/", rgsczPaths[0]); + NativeAssert::StringEqual(L"//server/share/subdir/", rgsczPaths[1]); + ReleaseNullStrArray(rgsczPaths, cPaths); + + hr = PathGetHierarchyArray(L"Software/Microsoft/Windows/ValueName", &rgsczPaths, &cPaths); + NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC directory path"); + Assert::Equal(4, cPaths); + NativeAssert::StringEqual(L"Software/", rgsczPaths[0]); + NativeAssert::StringEqual(L"Software/Microsoft/", rgsczPaths[1]); + NativeAssert::StringEqual(L"Software/Microsoft/Windows/", rgsczPaths[2]); + NativeAssert::StringEqual(L"Software/Microsoft/Windows/ValueName", rgsczPaths[3]); + ReleaseNullStrArray(rgsczPaths, cPaths); + + hr = PathGetHierarchyArray(L"Software/Microsoft/Windows/", &rgsczPaths, &cPaths); + NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC directory path"); + Assert::Equal(3, cPaths); + NativeAssert::StringEqual(L"Software/", rgsczPaths[0]); + NativeAssert::StringEqual(L"Software/Microsoft/", rgsczPaths[1]); + NativeAssert::StringEqual(L"Software/Microsoft/Windows/", rgsczPaths[2]); + ReleaseNullStrArray(rgsczPaths, cPaths); } finally { @@ -110,12 +445,66 @@ namespace DutilTests } } + [Fact] + void PathNormalizeSlashesFixedTest() + { + HRESULT hr = S_OK; + LPWSTR sczPath = NULL; + LPCWSTR rgwzPaths[54] = + { + L"", L"", + L"\\", L"\\", + L"\\\\", L"\\\\", + L"\\\\\\", L"\\\\\\", + L"\\\\?\\UNC\\", L"\\\\?\\UNC\\", + L"C:\\\\foo2", L"C:\\foo2", + L"\\\\?\\C:\\\\foo2", L"\\\\?\\C:\\foo2", + L"\\\\a\\b\\", L"\\\\a\\b\\", + L"\\\\?\\UNC\\a\\b\\\\c\\", L"\\\\?\\UNC\\a\\b\\c\\", + L"\\\\?\\UNC\\a\\b\\\\", L"\\\\?\\UNC\\a\\b\\", + L"\\\\?\\UNC\\test\\unc\\path\\to\\\\something", L"\\\\?\\UNC\\test\\unc\\path\\to\\something", + L"\\\\?\\C:\\\\foo\\\\bar.txt", L"\\\\?\\C:\\foo\\bar.txt", + L"\\??\\C:\\\\foo\\bar.txt", L"\\??\\C:\\foo\\bar.txt", + L"\\??\\\\C:\\\\foo\\bar.txt", L"\\??\\\\C:\\foo\\bar.txt", + L"/", L"\\", + L"//", L"\\\\", + L"///", L"\\\\\\", + L"//?/UNC/", L"\\\\?\\UNC\\", + L"C://foo2", L"C:\\foo2", + L"//?/C://foo2", L"\\\\?\\C:\\foo2", + L"//a/b/", L"\\\\a\\b\\", + L"//?/UNC/a/b//c/", L"\\\\?\\UNC\\a\\b\\c\\", + L"//?/UNC/a/b//", L"\\\\?\\UNC\\a\\b\\", + L"//?/UNC/test/unc/path/to//something", L"\\\\?\\UNC\\test\\unc\\path\\to\\something", + L"//?/C://foo//bar.txt", L"\\\\?\\C:\\foo\\bar.txt", + L"/??/C://foo/bar.txt", L"\\??\\C:\\foo\\bar.txt", + L"/??//C://foo/bar.txt", L"\\??\\\\C:\\foo\\bar.txt", + }; + + try + { + for (DWORD i = 0; i < countof(rgwzPaths); i += 2) + { + hr = StrAllocString(&sczPath, rgwzPaths[i], 0); + NativeAssert::Succeeded(hr, "Failed to copy string"); + + hr = PathFixedNormalizeSlashes(sczPath); + NativeAssert::Succeeded(hr, "PathNormalizeSlashes: {0}", rgwzPaths[i]); + NativeAssert::StringEqual(rgwzPaths[i + 1], sczPath); + } + } + finally + { + ReleaseStr(sczPath); + } + } + [Fact] void PathPrefixTest() { HRESULT hr = S_OK; LPWSTR sczPath = NULL; - LPCWSTR rgwzPaths[12] = + LPCWSTR rgwzPaths[24] = { L"\\\\", L"\\\\?\\UNC\\", L"C:\\\\foo2", L"\\\\?\\C:\\\\foo2", @@ -123,11 +512,17 @@ namespace DutilTests L"\\\\?\\UNC\\test\\unc\\path\\to\\something", L"\\\\?\\UNC\\test\\unc\\path\\to\\something", L"\\\\?\\C:\\foo\\bar.txt", L"\\\\?\\C:\\foo\\bar.txt", L"\\??\\C:\\foo\\bar.txt", L"\\??\\C:\\foo\\bar.txt", + L"//", L"\\\\?\\UNC\\", + L"C://foo2", L"\\\\?\\C://foo2", + L"//a/b/", L"\\\\?\\UNC\\a/b/", + L"//?/UNC/test/unc/path/to/something", L"//?/UNC/test/unc/path/to/something", + L"//?/C:/foo/bar.txt", L"//?/C:/foo/bar.txt", + L"/??/C:/foo/bar.txt", L"/??/C:/foo/bar.txt", }; try { - for (DWORD i = 0; i < countof(rgwzPaths) / 2; i += 2) + for (DWORD i = 0; i < countof(rgwzPaths); i += 2) { hr = StrAllocString(&sczPath, rgwzPaths[i], 0); NativeAssert::Succeeded(hr, "Failed to copy string"); @@ -148,16 +543,20 @@ namespace DutilTests { HRESULT hr = S_OK; LPWSTR sczPath = NULL; - LPCWSTR rgwzPaths[8] = + LPCWSTR rgwzPaths[12] = { L"\\", + L"/", L"C:", L"C:foo.txt", L"", L"\\?", + L"/?", L"\\dir", + L"/dir", L"dir", L"dir\\subdir", + L"dir/subdir", }; try @@ -180,93 +579,162 @@ 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", + 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", }; - for (DWORD i = 0; i < countof(rgwzPaths); ++i) + try { - ValidateFullyQualifiedPath(rgwzPaths[i], TRUE, FALSE); - ValidateRootedPath(rgwzPaths[i], TRUE); + 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); } } [Fact] void PathIsRootedAndFullyQualifiedWithPrefixTest() { + HRESULT hr = S_OK; + LPWSTR sczPath = NULL; LPCWSTR rgwzPaths[6] = { - 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"//?/UNC/test/unc", + L"//?/UNC/a/b1", + L"//?/UNC/a/b2/", + L"//?/C:/foo/bar.txt", + L"/??/C:/foo/bar.txt", }; - for (DWORD i = 0; i < countof(rgwzPaths); ++i) + try { - ValidateFullyQualifiedPath(rgwzPaths[i], TRUE, TRUE); - ValidateRootedPath(rgwzPaths[i], TRUE); + 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); } } [Fact] void PathIsRootedButNotFullyQualifiedTest() { + HRESULT hr = S_OK; + LPWSTR sczPath = NULL; LPCWSTR rgwzPaths[7] = { - L"\\", + L"/", L"a:", L"A:", L"z:", L"Z:", L"C:foo.txt", - L"\\dir", + L"/dir", }; - for (DWORD i = 0; i < countof(rgwzPaths); ++i) + try { - ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE); - ValidateRootedPath(rgwzPaths[i], TRUE); + 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); } } [Fact] void PathIsNotRootedAndNotFullyQualifiedTest() { + HRESULT hr = S_OK; + LPWSTR sczPath = NULL; LPCWSTR rgwzPaths[9] = { 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"dir/subdir", + L"@:/foo", // 064 = @ 065 = A + L"[://", // 091 = [ 090 = Z + L"`:/foo ", // 096 = ` 097 = a + L"{://", // 123 = { 122 = z L"[:", }; - for (DWORD i = 0; i < countof(rgwzPaths); ++i) + try { - ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE); - ValidateRootedPath(rgwzPaths[i], FALSE); + for (DWORD i = 0; i < countof(rgwzPaths); ++i) + { + ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE); + ValidateRootedPath(rgwzPaths[i], FALSE); + + if (!rgwzPaths[i]) + { + continue; + } + + hr = StrAllocString(&sczPath, rgwzPaths[i], 0); + NativeAssert::Succeeded(hr, "Failed to copy string"); + + PathFixedReplaceForwardSlashes(sczPath); + ValidateFullyQualifiedPath(sczPath, FALSE, FALSE); + ValidateRootedPath(sczPath, FALSE); + } + } + finally + { + ReleaseStr(sczPath); } } diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs index 5b663d93..a4f6a3fd 100644 --- a/src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs +++ b/src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs @@ -84,7 +84,7 @@ namespace WixToolsetTest.CoreIntegration ? field.AsNullableNumber()?.ToString() : field?.AsString()) .ToList(); - Assert.Equal(@"c\d.exe", fields[(int)WixBundlePayloadSymbolFields.Name]); + Assert.Equal(@"c\d\e\f.exe", fields[(int)WixBundlePayloadSymbolFields.Name]); } } diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs index 544b80ec..a6f3b453 100644 --- a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs @@ -1,7 +1,7 @@ - + -- cgit v1.2.3-55-g6feb