From 90982fbf1c887a3ed3454f9ab3ab8dfbd57a1383 Mon Sep 17 00:00:00 2001
From: Sean Hall <r.sean.hall@gmail.com>
Date: Thu, 26 May 2022 17:34:48 -0500
Subject: Add PathConcatRelativeToBase and use it in Burn.

Fixes 6707
---
 src/libs/dutil/WixToolset.DUtil/inc/pathutil.h     |  11 +++
 src/libs/dutil/WixToolset.DUtil/path2utl.cpp       |  40 ++++++++
 src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp | 107 +++++++++++++++++++++
 3 files changed, 158 insertions(+)

(limited to 'src/libs')

diff --git a/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h b/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h
index fc6bb3bb..941793f8 100644
--- a/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h
+++ b/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h
@@ -236,6 +236,17 @@ DAPI_(HRESULT) PathConcatCch(
     __deref_out_z LPWSTR* psczCombined
     );
 
+/*******************************************************************
+ PathConcatRelativeToBase - canonicalizes a relative path before
+    concatenating it to the base path to ensure the resulting path
+    is inside the base path.
+*******************************************************************/
+DAPI_(HRESULT) PathConcatRelativeToBase(
+    __in LPCWSTR wzBase,
+    __in_opt LPCWSTR wzRelative,
+    __deref_out_z LPWSTR* psczCombined
+    );
+
 /*******************************************************************
  PathCompare - compares the fully expanded path of the two paths using
                ::CompareStringW().
diff --git a/src/libs/dutil/WixToolset.DUtil/path2utl.cpp b/src/libs/dutil/WixToolset.DUtil/path2utl.cpp
index 45157d0b..61c1803a 100644
--- a/src/libs/dutil/WixToolset.DUtil/path2utl.cpp
+++ b/src/libs/dutil/WixToolset.DUtil/path2utl.cpp
@@ -131,6 +131,46 @@ LExit:
     return hr;
 }
 
+DAPI_(HRESULT) PathConcatRelativeToBase(
+    __in LPCWSTR wzBase,
+    __in_opt LPCWSTR wzRelative,
+    __deref_out_z LPWSTR* psczCombined
+    )
+{
+    HRESULT hr = S_OK;
+    LPWSTR sczCanonicalizedRelative = NULL;
+
+    if (!wzBase || !*wzBase)
+    {
+        PathExitWithRootFailure(hr, E_INVALIDARG, "wzBase is required.");
+    }
+
+    if (PathIsRooted(wzRelative))
+    {
+        PathExitWithRootFailure(hr, E_INVALIDARG, "wzRelative cannot be rooted.");
+    }
+
+    hr = StrAllocString(psczCombined, wzBase, 0);
+    PathExitOnFailure(hr, "Failed to copy base to output.");
+
+    if (wzRelative && *wzRelative)
+    {
+        hr = PathBackslashTerminate(psczCombined);
+        PathExitOnFailure(hr, "Failed to backslashify.");
+
+        hr = PathCanonicalizeForComparison(wzRelative, 0, &sczCanonicalizedRelative);
+        PathExitOnFailure(hr, "Failed to canonicalize wzRelative.");
+
+        hr = StrAllocConcat(psczCombined, sczCanonicalizedRelative, 0);
+        PathExitOnFailure(hr, "Failed to append relative to output.");
+    }
+
+LExit:
+    ReleaseStr(sczCanonicalizedRelative);
+
+    return hr;
+}
+
 DAPI_(HRESULT) PathDirectoryContainsPath(
     __in_z LPCWSTR wzDirectory,
     __in_z LPCWSTR wzPath
diff --git a/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
index 04d0b447..52698b98 100644
--- a/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
+++ b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
@@ -220,6 +220,113 @@ namespace DutilTests
             }
         }
 
+        [Fact]
+        void PathConcatTest()
+        {
+            HRESULT hr = S_OK;
+            LPWSTR sczPath = NULL;
+            LPCWSTR rgwzPaths[54] =
+            {
+                L"a", NULL, L"a",
+                L"a", L"", L"a",
+                L"C:\\", L"a", L"C:\\a",
+                L"\\a", L"b", L"\\a\\b",
+                L"a", L"b", L"a\\b",
+                L"C:\\", L"..\\a", L"C:\\..\\a",
+                L"C:\\a", L"..\\b", L"C:\\a\\..\\b",
+                L"\\\\server\\share", L"..\\a", L"\\\\server\\share\\..\\a",
+                L"\\\\server\\share\\a", L"..\\b", L"\\\\server\\share\\a\\..\\b",
+                NULL, L"b", L"b",
+                L"", L"b", L"b",
+                L"a", L"\\b", L"\\b",
+                L"a", L"b:", L"b:",
+                L"a", L"b:\\", L"b:\\",
+                L"a", L"\\\\?\\b", L"\\\\?\\b",
+                L"a", L"\\\\?\\UNC\\b", L"\\\\?\\UNC\\b",
+                L"a", L"\\b", L"\\b",
+                L"a", L"\\\\", L"\\\\",
+            };
+
+            try
+            {
+                for (DWORD i = 0; i < countof(rgwzPaths); i += 3)
+                {
+                    hr = PathConcat(rgwzPaths[i], rgwzPaths[i + 1], &sczPath);
+                    NativeAssert::Succeeded(hr, "PathConcat: {0}, {1}", rgwzPaths[i], rgwzPaths[i + 1]);
+                    NativeAssert::StringEqual(rgwzPaths[i + 2], sczPath);
+                }
+            }
+            finally
+            {
+                ReleaseStr(sczPath);
+            }
+        }
+
+        [Fact]
+        void PathConcatRelativeToBaseTest()
+        {
+            HRESULT hr = S_OK;
+            LPWSTR sczPath = NULL;
+            LPCWSTR rgwzPaths[27] =
+            {
+                L"a", NULL, L"a",
+                L"a", L"", L"a",
+                L"C:\\", L"a", L"C:\\a",
+                L"\\a", L"b", L"\\a\\b",
+                L"a", L"b", L"a\\b",
+                L"C:\\", L"..\\a", L"C:\\a",
+                L"C:\\a", L"..\\b", L"C:\\a\\b",
+                L"\\\\server\\share", L"..\\a", L"\\\\server\\share\\a",
+                L"\\\\server\\share\\a", L"..\\b", L"\\\\server\\share\\a\\b",
+            };
+
+            try
+            {
+                for (DWORD i = 0; i < countof(rgwzPaths); i += 3)
+                {
+                    hr = PathConcatRelativeToBase(rgwzPaths[i], rgwzPaths[i + 1], &sczPath);
+                    NativeAssert::Succeeded(hr, "PathConcatRelativeToBase: {0}, {1}", rgwzPaths[i], rgwzPaths[i + 1]);
+                    NativeAssert::StringEqual(rgwzPaths[i + 2], sczPath);
+                }
+            }
+            finally
+            {
+                ReleaseStr(sczPath);
+            }
+        }
+
+        [Fact]
+        void PathConcatRelativeToBaseFailureTest()
+        {
+            HRESULT hr = S_OK;
+            LPWSTR sczPath = NULL;
+            LPCWSTR rgwzPaths[18] =
+            {
+                NULL, L"b",
+                L"", L"b",
+                L"a", L"\\b",
+                L"a", L"b:",
+                L"a", L"b:\\",
+                L"a", L"\\\\?\\b",
+                L"a", L"\\\\?\\UNC\\b",
+                L"a", L"\\b",
+                L"a", L"\\\\",
+            };
+
+            try
+            {
+                for (DWORD i = 0; i < countof(rgwzPaths); i += 2)
+                {
+                    hr = PathConcatRelativeToBase(rgwzPaths[i], rgwzPaths[i + 1], &sczPath);
+                    NativeAssert::SpecificReturnCode(hr, E_INVALIDARG, "PathConcatRelativeToBase: {0}, {1}", rgwzPaths[i], rgwzPaths[i + 1]);
+                }
+            }
+            finally
+            {
+                ReleaseStr(sczPath);
+            }
+        }
+
         [Fact]
         void PathDirectoryContainsPathTest()
         {
-- 
cgit v1.2.3-55-g6feb