summaryrefslogtreecommitdiffstats
path: root/winpr/libwinpr/path
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
commita9bcc81f821d7c66f623779fa5147e728eb3c388 (patch)
tree98676963bcdd537ae5908a067a8eb110b93486a6 /winpr/libwinpr/path
parentInitial commit. (diff)
downloadfreerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz
freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--winpr/libwinpr/path/CMakeLists.txt26
-rw-r--r--winpr/libwinpr/path/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/path/include/PathAllocCombine.c180
-rw-r--r--winpr/libwinpr/path/include/PathCchAddExtension.c101
-rw-r--r--winpr/libwinpr/path/include/PathCchAddSeparator.c64
-rw-r--r--winpr/libwinpr/path/include/PathCchAddSeparatorEx.c66
-rw-r--r--winpr/libwinpr/path/include/PathCchAppend.c131
-rw-r--r--winpr/libwinpr/path/path.c1181
-rw-r--r--winpr/libwinpr/path/shell.c821
-rw-r--r--winpr/libwinpr/path/shell_ios.h9
-rw-r--r--winpr/libwinpr/path/shell_ios.m54
-rw-r--r--winpr/libwinpr/path/test/CMakeLists.txt48
-rw-r--r--winpr/libwinpr/path/test/TestPathAllocCanonicalize.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathAllocCombine.c98
-rw-r--r--winpr/libwinpr/path/test/TestPathCchAddBackslash.c100
-rw-r--r--winpr/libwinpr/path/test/TestPathCchAddBackslashEx.c103
-rw-r--r--winpr/libwinpr/path/test/TestPathCchAddExtension.c140
-rw-r--r--winpr/libwinpr/path/test/TestPathCchAppend.c151
-rw-r--r--winpr/libwinpr/path/test/TestPathCchAppendEx.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchCanonicalize.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchCanonicalizeEx.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchCombine.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchCombineEx.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchFindExtension.c116
-rw-r--r--winpr/libwinpr/path/test/TestPathCchIsRoot.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchRemoveBackslash.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchRemoveBackslashEx.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchRemoveExtension.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchRemoveFileSpec.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchRenameExtension.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchSkipRoot.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchStripPrefix.c128
-rw-r--r--winpr/libwinpr/path/test/TestPathCchStripToRoot.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathIsUNCEx.c52
-rw-r--r--winpr/libwinpr/path/test/TestPathMakePath.c83
-rw-r--r--winpr/libwinpr/path/test/TestPathShell.c58
36 files changed, 3887 insertions, 0 deletions
diff --git a/winpr/libwinpr/path/CMakeLists.txt b/winpr/libwinpr/path/CMakeLists.txt
new file mode 100644
index 0000000..1e4ed92
--- /dev/null
+++ b/winpr/libwinpr/path/CMakeLists.txt
@@ -0,0 +1,26 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-path cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+winpr_module_add(path.c shell.c)
+
+if (IOS)
+ winpr_module_add(shell_ios.m)
+endif (IOS)
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/path/ModuleOptions.cmake b/winpr/libwinpr/path/ModuleOptions.cmake
new file mode 100644
index 0000000..b330a21
--- /dev/null
+++ b/winpr/libwinpr/path/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "path")
+set(MINWIN_LONG_NAME "Path Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/path/include/PathAllocCombine.c b/winpr/libwinpr/path/include/PathAllocCombine.c
new file mode 100644
index 0000000..abdbd29
--- /dev/null
+++ b/winpr/libwinpr/path/include/PathAllocCombine.c
@@ -0,0 +1,180 @@
+
+/*
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR '\\'
+#define CUR_PATH_SEPARATOR_STR "\\"
+#define PATH_ALLOC_COMBINE PathAllocCombineA
+*/
+
+/**
+ * FIXME: These implementations of the PathAllocCombine functions have
+ * several issues:
+ * - pszPathIn or pszMore may be NULL (but not both)
+ * - no check if pszMore is fully qualified (if so, it must be directly
+ * copied to the output buffer without being combined with pszPathIn.
+ * - if pszMore begins with a _single_ backslash it must be combined with
+ * only the root of the path pointed to by pszPathIn and there's no code
+ * to extract the root of pszPathIn.
+ * - the function will crash with some short string lengths of the parameters
+ */
+
+#if DEFINE_UNICODE
+
+HRESULT PATH_ALLOC_COMBINE(PCWSTR pszPathIn, PCWSTR pszMore, unsigned long dwFlags,
+ PWSTR* ppszPathOut)
+{
+ PWSTR pszPathOut;
+ BOOL backslashIn;
+ BOOL backslashMore;
+ size_t pszMoreLength;
+ size_t pszPathInLength;
+ size_t pszPathOutLength;
+ WLog_WARN(TAG, "has known bugs and needs fixing.");
+
+ if (!ppszPathOut)
+ return E_INVALIDARG;
+
+ if (!pszPathIn && !pszMore)
+ return E_INVALIDARG;
+
+ if (!pszMore)
+ return E_FAIL; /* valid but not implemented, see top comment */
+
+ if (!pszPathIn)
+ return E_FAIL; /* valid but not implemented, see top comment */
+
+ pszPathInLength = _wcslen(pszPathIn);
+ pszMoreLength = _wcslen(pszMore);
+
+ /* prevent segfaults - the complete implementation below is buggy */
+ if (pszPathInLength < 3)
+ return E_FAIL;
+
+ backslashIn = (pszPathIn[pszPathInLength - 1] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+ backslashMore = (pszMore[0] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+
+ if (backslashMore)
+ {
+ if ((pszPathIn[1] == ':') && (pszPathIn[2] == CUR_PATH_SEPARATOR_CHR))
+ {
+ const WCHAR colon[] = { ':', '\0' };
+ size_t sizeOfBuffer;
+ pszPathOutLength = sizeof(WCHAR) + pszMoreLength;
+ sizeOfBuffer = (pszPathOutLength + 1) * sizeof(WCHAR);
+ pszPathOut = (PWSTR)calloc(sizeOfBuffer, sizeof(WCHAR));
+
+ if (!pszPathOut)
+ return E_OUTOFMEMORY;
+
+ _wcsncat(pszPathOut, &pszPathIn[0], 1);
+ _wcsncat(pszPathOut, colon, ARRAYSIZE(colon));
+ _wcsncat(pszPathOut, pszMore, pszMoreLength);
+ *ppszPathOut = pszPathOut;
+ return S_OK;
+ }
+ }
+ else
+ {
+ const WCHAR sep[] = CUR_PATH_SEPARATOR_STR;
+ size_t sizeOfBuffer;
+ pszPathOutLength = pszPathInLength + pszMoreLength;
+ sizeOfBuffer = (pszPathOutLength + 1) * 2;
+ pszPathOut = (PWSTR)calloc(sizeOfBuffer, 2);
+
+ if (!pszPathOut)
+ return E_OUTOFMEMORY;
+
+ _wcsncat(pszPathOut, pszPathIn, pszPathInLength);
+ if (!backslashIn)
+ _wcsncat(pszPathOut, sep, ARRAYSIZE(sep));
+ _wcsncat(pszPathOut, pszMore, pszMoreLength);
+
+ *ppszPathOut = pszPathOut;
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+#else
+
+HRESULT PATH_ALLOC_COMBINE(PCSTR pszPathIn, PCSTR pszMore, unsigned long dwFlags, PSTR* ppszPathOut)
+{
+ PSTR pszPathOut;
+ BOOL backslashIn;
+ BOOL backslashMore;
+ int pszMoreLength;
+ int pszPathInLength;
+ int pszPathOutLength;
+ WLog_WARN(TAG, "has known bugs and needs fixing.");
+
+ if (!ppszPathOut)
+ return E_INVALIDARG;
+
+ if (!pszPathIn && !pszMore)
+ return E_INVALIDARG;
+
+ if (!pszMore)
+ return E_FAIL; /* valid but not implemented, see top comment */
+
+ if (!pszPathIn)
+ return E_FAIL; /* valid but not implemented, see top comment */
+
+ pszPathInLength = strlen(pszPathIn);
+ pszMoreLength = strlen(pszMore);
+
+ /* prevent segfaults - the complete implementation below is buggy */
+ if (pszPathInLength < 3)
+ return E_FAIL;
+
+ backslashIn = (pszPathIn[pszPathInLength - 1] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+ backslashMore = (pszMore[0] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+
+ if (backslashMore)
+ {
+ if ((pszPathIn[1] == ':') && (pszPathIn[2] == CUR_PATH_SEPARATOR_CHR))
+ {
+ size_t sizeOfBuffer;
+ pszPathOutLength = 2 + pszMoreLength;
+ sizeOfBuffer = (pszPathOutLength + 1) * 2;
+ pszPathOut = (PSTR)calloc(sizeOfBuffer, 2);
+
+ if (!pszPathOut)
+ return E_OUTOFMEMORY;
+
+ sprintf_s(pszPathOut, sizeOfBuffer, "%c:%s", pszPathIn[0], pszMore);
+ *ppszPathOut = pszPathOut;
+ return S_OK;
+ }
+ }
+ else
+ {
+ size_t sizeOfBuffer;
+ pszPathOutLength = pszPathInLength + pszMoreLength;
+ sizeOfBuffer = (pszPathOutLength + 1) * 2;
+ pszPathOut = (PSTR)calloc(sizeOfBuffer, 2);
+
+ if (!pszPathOut)
+ return E_OUTOFMEMORY;
+
+ if (backslashIn)
+ sprintf_s(pszPathOut, sizeOfBuffer, "%s%s", pszPathIn, pszMore);
+ else
+ sprintf_s(pszPathOut, sizeOfBuffer, "%s" CUR_PATH_SEPARATOR_STR "%s", pszPathIn,
+ pszMore);
+
+ *ppszPathOut = pszPathOut;
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+#endif
+
+/*
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_ALLOC_COMBINE
+*/
diff --git a/winpr/libwinpr/path/include/PathCchAddExtension.c b/winpr/libwinpr/path/include/PathCchAddExtension.c
new file mode 100644
index 0000000..498cfab
--- /dev/null
+++ b/winpr/libwinpr/path/include/PathCchAddExtension.c
@@ -0,0 +1,101 @@
+
+/*
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR '\\'
+#define PATH_CCH_ADD_EXTENSION PathCchAddExtensionA
+*/
+
+#if DEFINE_UNICODE
+
+HRESULT PATH_CCH_ADD_EXTENSION(PWSTR pszPath, size_t cchPath, PCWSTR pszExt)
+{
+ LPWSTR pDot;
+ BOOL bExtDot;
+ LPWSTR pBackslash;
+ size_t pszExtLength;
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ if (!pszExt)
+ return E_INVALIDARG;
+
+ pszExtLength = _wcslen(pszExt);
+ pszPathLength = _wcslen(pszPath);
+ bExtDot = (pszExt[0] == '.') ? TRUE : FALSE;
+
+ pDot = _wcsrchr(pszPath, '.');
+ pBackslash = _wcsrchr(pszPath, CUR_PATH_SEPARATOR_CHR);
+
+ if (pDot && pBackslash)
+ {
+ if (pDot > pBackslash)
+ return S_FALSE;
+ }
+
+ if (cchPath > pszPathLength + pszExtLength + ((bExtDot) ? 0 : 1))
+ {
+ const WCHAR dot[] = { '.', '\0' };
+ WCHAR* ptr = &pszPath[pszPathLength];
+ *ptr = '\0';
+
+ if (!bExtDot)
+ _wcsncat(ptr, dot, _wcslen(dot));
+ _wcsncat(ptr, pszExt, pszExtLength);
+
+ return S_OK;
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+}
+
+#else
+
+HRESULT PATH_CCH_ADD_EXTENSION(PSTR pszPath, size_t cchPath, PCSTR pszExt)
+{
+ CHAR* pDot;
+ BOOL bExtDot;
+ CHAR* pBackslash;
+ size_t pszExtLength;
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ if (!pszExt)
+ return E_INVALIDARG;
+
+ pszExtLength = strlen(pszExt);
+ pszPathLength = strlen(pszPath);
+ bExtDot = (pszExt[0] == '.') ? TRUE : FALSE;
+
+ pDot = strrchr(pszPath, '.');
+ pBackslash = strrchr(pszPath, CUR_PATH_SEPARATOR_CHR);
+
+ if (pDot && pBackslash)
+ {
+ if (pDot > pBackslash)
+ return S_FALSE;
+ }
+
+ if (cchPath > pszPathLength + pszExtLength + ((bExtDot) ? 0 : 1))
+ {
+ if (bExtDot)
+ sprintf_s(&pszPath[pszPathLength], cchPath - pszPathLength, "%s", pszExt);
+ else
+ sprintf_s(&pszPath[pszPathLength], cchPath - pszPathLength, ".%s", pszExt);
+
+ return S_OK;
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+}
+
+#endif
+
+/*
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_EXTENSION
+*/
diff --git a/winpr/libwinpr/path/include/PathCchAddSeparator.c b/winpr/libwinpr/path/include/PathCchAddSeparator.c
new file mode 100644
index 0000000..0ef391c
--- /dev/null
+++ b/winpr/libwinpr/path/include/PathCchAddSeparator.c
@@ -0,0 +1,64 @@
+
+/*
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR '\\'
+#define PATH_CCH_ADD_SEPARATOR PathCchAddBackslashA
+*/
+
+#if DEFINE_UNICODE
+
+HRESULT PATH_CCH_ADD_SEPARATOR(PWSTR pszPath, size_t cchPath)
+{
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ pszPathLength = _wcslen(pszPath);
+
+ if (pszPath[pszPathLength - 1] == CUR_PATH_SEPARATOR_CHR)
+ return S_FALSE;
+
+ if (cchPath > (pszPathLength + 1))
+ {
+ pszPath[pszPathLength] = CUR_PATH_SEPARATOR_CHR;
+ pszPath[pszPathLength + 1] = '\0';
+
+ return S_OK;
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+}
+
+#else
+
+HRESULT PATH_CCH_ADD_SEPARATOR(PSTR pszPath, size_t cchPath)
+{
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ pszPathLength = strlen(pszPath);
+
+ if (pszPath[pszPathLength - 1] == CUR_PATH_SEPARATOR_CHR)
+ return S_FALSE;
+
+ if (cchPath > (pszPathLength + 1))
+ {
+ pszPath[pszPathLength] = CUR_PATH_SEPARATOR_CHR;
+ pszPath[pszPathLength + 1] = '\0';
+
+ return S_OK;
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+}
+
+#endif
+
+/*
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR
+*/
diff --git a/winpr/libwinpr/path/include/PathCchAddSeparatorEx.c b/winpr/libwinpr/path/include/PathCchAddSeparatorEx.c
new file mode 100644
index 0000000..02832d8
--- /dev/null
+++ b/winpr/libwinpr/path/include/PathCchAddSeparatorEx.c
@@ -0,0 +1,66 @@
+
+/*
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR '\\'
+#define PATH_CCH_ADD_SEPARATOR_EX PathCchAddBackslashExA
+*/
+
+#if DEFINE_UNICODE
+
+HRESULT PATH_CCH_ADD_SEPARATOR_EX(PWSTR pszPath, size_t cchPath, PWSTR* ppszEnd,
+ size_t* pcchRemaining)
+{
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ pszPathLength = _wcslen(pszPath);
+
+ if (pszPath[pszPathLength - 1] == CUR_PATH_SEPARATOR_CHR)
+ return S_FALSE;
+
+ if (cchPath > (pszPathLength + 1))
+ {
+ pszPath[pszPathLength] = CUR_PATH_SEPARATOR_CHR;
+ pszPath[pszPathLength + 1] = '\0';
+
+ return S_OK;
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+}
+
+#else
+
+HRESULT PATH_CCH_ADD_SEPARATOR_EX(PSTR pszPath, size_t cchPath, PSTR* ppszEnd,
+ size_t* pcchRemaining)
+{
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ pszPathLength = strlen(pszPath);
+
+ if (pszPath[pszPathLength - 1] == CUR_PATH_SEPARATOR_CHR)
+ return S_FALSE;
+
+ if (cchPath > (pszPathLength + 1))
+ {
+ pszPath[pszPathLength] = CUR_PATH_SEPARATOR_CHR;
+ pszPath[pszPathLength + 1] = '\0';
+
+ return S_OK;
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+}
+
+#endif
+
+/*
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR_EX
+*/
diff --git a/winpr/libwinpr/path/include/PathCchAppend.c b/winpr/libwinpr/path/include/PathCchAppend.c
new file mode 100644
index 0000000..a4f58cb
--- /dev/null
+++ b/winpr/libwinpr/path/include/PathCchAppend.c
@@ -0,0 +1,131 @@
+
+/*
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR '\\'
+#define CUR_PATH_SEPARATOR_STR "\\"
+#define PATH_CCH_APPEND PathCchAppendA
+*/
+
+#if DEFINE_UNICODE
+
+HRESULT PATH_CCH_APPEND(PWSTR pszPath, size_t cchPath, PCWSTR pszMore)
+{
+ BOOL pathBackslash;
+ BOOL moreBackslash;
+ size_t pszMoreLength;
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ if (!pszMore)
+ return E_INVALIDARG;
+
+ if (cchPath == 0 || cchPath > PATHCCH_MAX_CCH)
+ return E_INVALIDARG;
+
+ pszMoreLength = _wcslen(pszMore);
+ pszPathLength = _wcslen(pszPath);
+
+ pathBackslash = (pszPath[pszPathLength - 1] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+ moreBackslash = (pszMore[0] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+
+ if (pathBackslash && moreBackslash)
+ {
+ if ((pszPathLength + pszMoreLength - 1) < cchPath)
+ {
+ WCHAR* ptr = &pszPath[pszPathLength];
+ *ptr = '\0';
+ _wcsncat(ptr, &pszMore[1], _wcslen(&pszMore[1]));
+ return S_OK;
+ }
+ }
+ else if ((pathBackslash && !moreBackslash) || (!pathBackslash && moreBackslash))
+ {
+ if ((pszPathLength + pszMoreLength) < cchPath)
+ {
+ WCHAR* ptr = &pszPath[pszPathLength];
+ *ptr = '\0';
+ _wcsncat(ptr, pszMore, _wcslen(pszMore));
+ return S_OK;
+ }
+ }
+ else if (!pathBackslash && !moreBackslash)
+ {
+ if ((pszPathLength + pszMoreLength + 1) < cchPath)
+ {
+ const WCHAR sep[] = CUR_PATH_SEPARATOR_STR;
+ WCHAR* ptr = &pszPath[pszPathLength];
+ *ptr = '\0';
+ _wcsncat(ptr, sep, _wcslen(sep));
+ _wcsncat(ptr, pszMore, _wcslen(pszMore));
+ return S_OK;
+ }
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
+}
+
+#else
+
+HRESULT PATH_CCH_APPEND(PSTR pszPath, size_t cchPath, PCSTR pszMore)
+{
+ BOOL pathBackslash = FALSE;
+ BOOL moreBackslash = FALSE;
+ size_t pszMoreLength;
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ if (!pszMore)
+ return E_INVALIDARG;
+
+ if (cchPath == 0 || cchPath > PATHCCH_MAX_CCH)
+ return E_INVALIDARG;
+
+ pszPathLength = strlen(pszPath);
+ if (pszPathLength > 0)
+ pathBackslash = (pszPath[pszPathLength - 1] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+
+ pszMoreLength = strlen(pszMore);
+ if (pszMoreLength > 0)
+ moreBackslash = (pszMore[0] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+
+ if (pathBackslash && moreBackslash)
+ {
+ if ((pszPathLength + pszMoreLength - 1) < cchPath)
+ {
+ sprintf_s(&pszPath[pszPathLength], cchPath - pszPathLength, "%s", &pszMore[1]);
+ return S_OK;
+ }
+ }
+ else if ((pathBackslash && !moreBackslash) || (!pathBackslash && moreBackslash))
+ {
+ if ((pszPathLength + pszMoreLength) < cchPath)
+ {
+ sprintf_s(&pszPath[pszPathLength], cchPath - pszPathLength, "%s", pszMore);
+ return S_OK;
+ }
+ }
+ else if (!pathBackslash && !moreBackslash)
+ {
+ if ((pszPathLength + pszMoreLength + 1) < cchPath)
+ {
+ sprintf_s(&pszPath[pszPathLength], cchPath - pszPathLength, CUR_PATH_SEPARATOR_STR "%s",
+ pszMore);
+ return S_OK;
+ }
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
+}
+
+#endif
+
+/*
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_CCH_APPEND
+*/
diff --git a/winpr/libwinpr/path/path.c b/winpr/libwinpr/path/path.c
new file mode 100644
index 0000000..82e6be1
--- /dev/null
+++ b/winpr/libwinpr/path/path.c
@@ -0,0 +1,1181 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Path Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+
+#include <winpr/path.h>
+#include <winpr/file.h>
+
+#define PATH_SLASH_CHR '/'
+#define PATH_SLASH_STR "/"
+
+#define PATH_BACKSLASH_CHR '\\'
+#define PATH_BACKSLASH_STR "\\"
+
+#ifdef _WIN32
+#define PATH_SLASH_STR_W L"/"
+#define PATH_BACKSLASH_STR_W L"\\"
+#else
+#define PATH_SLASH_STR_W \
+ { \
+ '/', '\0' \
+ }
+#define PATH_BACKSLASH_STR_W \
+ { \
+ '\\', '\0' \
+ }
+#endif
+
+#ifdef _WIN32
+#define PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define PATH_SEPARATOR_STR PATH_BACKSLASH_STR
+#define PATH_SEPARATOR_STR_W PATH_BACKSLASH_STR_W
+#else
+#define PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define PATH_SEPARATOR_STR PATH_SLASH_STR
+#define PATH_SEPARATOR_STR_W PATH_SLASH_STR_W
+#endif
+
+#define SHARED_LIBRARY_EXT_DLL "dll"
+#define SHARED_LIBRARY_EXT_SO "so"
+#define SHARED_LIBRARY_EXT_DYLIB "dylib"
+
+#ifdef _WIN32
+#define SHARED_LIBRARY_EXT SHARED_LIBRARY_EXT_DLL
+#elif defined(__APPLE__)
+#define SHARED_LIBRARY_EXT SHARED_LIBRARY_EXT_DYLIB
+#else
+#define SHARED_LIBRARY_EXT SHARED_LIBRARY_EXT_SO
+#endif
+
+#include "../log.h"
+#define TAG WINPR_TAG("path")
+
+/*
+ * PathCchAddBackslash
+ */
+
+/* Windows-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR PathCchAddBackslashA
+#include "include/PathCchAddSeparator.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR PathCchAddBackslashW
+#include "include/PathCchAddSeparator.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR
+
+/* Unix-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR PathCchAddSlashA
+#include "include/PathCchAddSeparator.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR PathCchAddSlashW
+#include "include/PathCchAddSeparator.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR
+
+/* Native-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define PATH_CCH_ADD_SEPARATOR PathCchAddSeparatorA
+#include "include/PathCchAddSeparator.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define PATH_CCH_ADD_SEPARATOR PathCchAddSeparatorW
+#include "include/PathCchAddSeparator.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR
+
+/*
+ * PathCchRemoveBackslash
+ */
+
+HRESULT PathCchRemoveBackslashA(PSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchRemoveBackslashW(PWSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathCchAddBackslashEx
+ */
+
+/* Windows-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR_EX PathCchAddBackslashExA
+#include "include/PathCchAddSeparatorEx.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR_EX
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR_EX PathCchAddBackslashExW
+#include "include/PathCchAddSeparatorEx.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR_EX
+
+/* Unix-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR_EX PathCchAddSlashExA
+#include "include/PathCchAddSeparatorEx.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR_EX
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR_EX PathCchAddSlashExW
+#include "include/PathCchAddSeparatorEx.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR_EX
+
+/* Native-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define PATH_CCH_ADD_SEPARATOR_EX PathCchAddSeparatorExA
+#include "include/PathCchAddSeparatorEx.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR_EX
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define PATH_CCH_ADD_SEPARATOR_EX PathCchAddSeparatorExW
+#include "include/PathCchAddSeparatorEx.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR_EX
+
+HRESULT PathCchRemoveBackslashExA(PSTR pszPath, size_t cchPath, PSTR* ppszEnd,
+ size_t* pcchRemaining)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchRemoveBackslashExW(PWSTR pszPath, size_t cchPath, PWSTR* ppszEnd,
+ size_t* pcchRemaining)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathCchAddExtension
+ */
+
+/* Windows-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define PATH_CCH_ADD_EXTENSION PathCchAddExtensionA
+#include "include/PathCchAddExtension.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_EXTENSION
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define PATH_CCH_ADD_EXTENSION PathCchAddExtensionW
+#include "include/PathCchAddExtension.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_EXTENSION
+
+/* Unix-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define PATH_CCH_ADD_EXTENSION UnixPathCchAddExtensionA
+#include "include/PathCchAddExtension.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_EXTENSION
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define PATH_CCH_ADD_EXTENSION UnixPathCchAddExtensionW
+#include "include/PathCchAddExtension.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_EXTENSION
+
+/* Native-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define PATH_CCH_ADD_EXTENSION NativePathCchAddExtensionA
+#include "include/PathCchAddExtension.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_EXTENSION
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define PATH_CCH_ADD_EXTENSION NativePathCchAddExtensionW
+#include "include/PathCchAddExtension.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_EXTENSION
+
+/*
+ * PathCchAppend
+ */
+
+/* Windows-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_BACKSLASH_STR
+#define PATH_CCH_APPEND PathCchAppendA
+#include "include/PathCchAppend.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_CCH_APPEND
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_BACKSLASH_STR_W
+#define PATH_CCH_APPEND PathCchAppendW
+#include "include/PathCchAppend.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_CCH_APPEND
+
+/* Unix-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SLASH_STR
+#define PATH_CCH_APPEND UnixPathCchAppendA
+#include "include/PathCchAppend.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_CCH_APPEND
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SLASH_STR_W
+#define PATH_CCH_APPEND UnixPathCchAppendW
+#include "include/PathCchAppend.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_CCH_APPEND
+
+/* Native-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SEPARATOR_STR
+#define PATH_CCH_APPEND NativePathCchAppendA
+#include "include/PathCchAppend.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_CCH_APPEND
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SEPARATOR_STR_W
+#define PATH_CCH_APPEND NativePathCchAppendW
+#include "include/PathCchAppend.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_CCH_APPEND
+
+/*
+ * PathCchAppendEx
+ */
+
+HRESULT PathCchAppendExA(PSTR pszPath, size_t cchPath, PCSTR pszMore, unsigned long dwFlags)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchAppendExW(PWSTR pszPath, size_t cchPath, PCWSTR pszMore, unsigned long dwFlags)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathCchCanonicalize
+ */
+
+HRESULT PathCchCanonicalizeA(PSTR pszPathOut, size_t cchPathOut, PCSTR pszPathIn)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchCanonicalizeW(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathCchCanonicalizeEx
+ */
+
+HRESULT PathCchCanonicalizeExA(PSTR pszPathOut, size_t cchPathOut, PCSTR pszPathIn,
+ unsigned long dwFlags)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchCanonicalizeExW(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn,
+ unsigned long dwFlags)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathAllocCanonicalize
+ */
+
+HRESULT PathAllocCanonicalizeA(PCSTR pszPathIn, unsigned long dwFlags, PSTR* ppszPathOut)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathAllocCanonicalizeW(PCWSTR pszPathIn, unsigned long dwFlags, PWSTR* ppszPathOut)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathCchCombine
+ */
+
+HRESULT PathCchCombineA(PSTR pszPathOut, size_t cchPathOut, PCSTR pszPathIn, PCSTR pszMore)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchCombineW(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn, PCWSTR pszMore)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathCchCombineEx
+ */
+
+HRESULT PathCchCombineExA(PSTR pszPathOut, size_t cchPathOut, PCSTR pszPathIn, PCSTR pszMore,
+ unsigned long dwFlags)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchCombineExW(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn, PCWSTR pszMore,
+ unsigned long dwFlags)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathAllocCombine
+ */
+
+/* Windows-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_BACKSLASH_STR
+#define PATH_ALLOC_COMBINE PathAllocCombineA
+#include "include/PathAllocCombine.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_ALLOC_COMBINE
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_BACKSLASH_STR_W
+#define PATH_ALLOC_COMBINE PathAllocCombineW
+#include "include/PathAllocCombine.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_ALLOC_COMBINE
+
+/* Unix-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SLASH_STR
+#define PATH_ALLOC_COMBINE UnixPathAllocCombineA
+#include "include/PathAllocCombine.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_ALLOC_COMBINE
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SLASH_STR_W
+#define PATH_ALLOC_COMBINE UnixPathAllocCombineW
+#include "include/PathAllocCombine.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_ALLOC_COMBINE
+
+/* Native-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SEPARATOR_STR
+#define PATH_ALLOC_COMBINE NativePathAllocCombineA
+#include "include/PathAllocCombine.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_ALLOC_COMBINE
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SEPARATOR_STR_W
+#define PATH_ALLOC_COMBINE NativePathAllocCombineW
+#include "include/PathAllocCombine.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_ALLOC_COMBINE
+
+/**
+ * PathCchFindExtension
+ */
+
+HRESULT PathCchFindExtensionA(PCSTR pszPath, size_t cchPath, PCSTR* ppszExt)
+{
+ const char* p = (const char*)pszPath;
+
+ if (!pszPath || !cchPath || !ppszExt)
+ return E_INVALIDARG;
+
+ /* find end of string */
+
+ while (*p && --cchPath)
+ {
+ p++;
+ }
+
+ if (*p)
+ {
+ /* pszPath is not null terminated within the cchPath range */
+ return E_INVALIDARG;
+ }
+
+ /* If no extension is found, ppszExt must point to the string's terminating null */
+ *ppszExt = p;
+
+ /* search backwards for '.' */
+
+ while (p > pszPath)
+ {
+ if (*p == '.')
+ {
+ *ppszExt = (PCSTR)p;
+ break;
+ }
+
+ if ((*p == '\\') || (*p == '/') || (*p == ':'))
+ break;
+
+ p--;
+ }
+
+ return S_OK;
+}
+
+HRESULT PathCchFindExtensionW(PCWSTR pszPath, size_t cchPath, PCWSTR* ppszExt)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/**
+ * PathCchRenameExtension
+ */
+
+HRESULT PathCchRenameExtensionA(PSTR pszPath, size_t cchPath, PCSTR pszExt)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchRenameExtensionW(PWSTR pszPath, size_t cchPath, PCWSTR pszExt)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/**
+ * PathCchRemoveExtension
+ */
+
+HRESULT PathCchRemoveExtensionA(PSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchRemoveExtensionW(PWSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/**
+ * PathCchIsRoot
+ */
+
+BOOL PathCchIsRootA(PCSTR pszPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return FALSE;
+}
+
+BOOL PathCchIsRootW(PCWSTR pszPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return FALSE;
+}
+
+/**
+ * PathIsUNCEx
+ */
+
+BOOL PathIsUNCExA(PCSTR pszPath, PCSTR* ppszServer)
+{
+ if (!pszPath)
+ return FALSE;
+
+ if ((pszPath[0] == '\\') && (pszPath[1] == '\\'))
+ {
+ *ppszServer = &pszPath[2];
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL PathIsUNCExW(PCWSTR pszPath, PCWSTR* ppszServer)
+{
+ if (!pszPath)
+ return FALSE;
+
+ if ((pszPath[0] == '\\') && (pszPath[1] == '\\'))
+ {
+ *ppszServer = &pszPath[2];
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * PathCchSkipRoot
+ */
+
+HRESULT PathCchSkipRootA(PCSTR pszPath, PCSTR* ppszRootEnd)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchSkipRootW(PCWSTR pszPath, PCWSTR* ppszRootEnd)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/**
+ * PathCchStripToRoot
+ */
+
+HRESULT PathCchStripToRootA(PSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchStripToRootW(PWSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/**
+ * PathCchStripPrefix
+ */
+
+HRESULT PathCchStripPrefixA(PSTR pszPath, size_t cchPath)
+{
+ BOOL hasPrefix = 0;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ if (cchPath < 4 || cchPath > PATHCCH_MAX_CCH)
+ return E_INVALIDARG;
+
+ hasPrefix = ((pszPath[0] == '\\') && (pszPath[1] == '\\') && (pszPath[2] == '?') &&
+ (pszPath[3] == '\\'))
+ ? TRUE
+ : FALSE;
+
+ if (hasPrefix)
+ {
+ if (cchPath < 6)
+ return S_FALSE;
+
+ if (IsCharAlpha(pszPath[4]) && (pszPath[5] == ':')) /* like C: */
+ {
+ memmove_s(pszPath, cchPath, &pszPath[4], cchPath - 4);
+ /* since the passed pszPath must not necessarily be null terminated
+ * and we always have enough space after the strip we can always
+ * ensure the null termination of the stripped result
+ */
+ pszPath[cchPath - 4] = 0;
+ return S_OK;
+ }
+ }
+
+ return S_FALSE;
+}
+
+HRESULT PathCchStripPrefixW(PWSTR pszPath, size_t cchPath)
+{
+ BOOL hasPrefix = 0;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ if (cchPath < 4 || cchPath > PATHCCH_MAX_CCH)
+ return E_INVALIDARG;
+
+ hasPrefix = ((pszPath[0] == '\\') && (pszPath[1] == '\\') && (pszPath[2] == '?') &&
+ (pszPath[3] == '\\'))
+ ? TRUE
+ : FALSE;
+
+ if (hasPrefix)
+ {
+ int rc = 0;
+ if (cchPath < 6)
+ return S_FALSE;
+
+ rc = (_wcslen(&pszPath[4]) + 1);
+ if ((rc < 0) || ((INT64)cchPath < rc))
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+
+ if (IsCharAlphaW(pszPath[4]) && (pszPath[5] == L':')) /* like C: */
+ {
+ wmemmove_s(pszPath, cchPath, &pszPath[4], cchPath - 4);
+ /* since the passed pszPath must not necessarily be null terminated
+ * and we always have enough space after the strip we can always
+ * ensure the null termination of the stripped result
+ */
+ pszPath[cchPath - 4] = 0;
+ return S_OK;
+ }
+ }
+
+ return S_FALSE;
+}
+
+/**
+ * PathCchRemoveFileSpec
+ */
+
+HRESULT PathCchRemoveFileSpecA(PSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchRemoveFileSpecW(PWSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * Path Portability Functions
+ */
+
+/**
+ * PathCchConvertStyle
+ */
+
+HRESULT PathCchConvertStyleA(PSTR pszPath, size_t cchPath, unsigned long dwFlags)
+{
+ if (dwFlags == PATH_STYLE_WINDOWS)
+ {
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_SLASH_CHR)
+ pszPath[index] = PATH_BACKSLASH_CHR;
+ }
+ }
+ else if (dwFlags == PATH_STYLE_UNIX)
+ {
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_BACKSLASH_CHR)
+ pszPath[index] = PATH_SLASH_CHR;
+ }
+ }
+ else if (dwFlags == PATH_STYLE_NATIVE)
+ {
+#if (PATH_SEPARATOR_CHR == PATH_BACKSLASH_CHR)
+ /* Unix-style to Windows-style */
+
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_SLASH_CHR)
+ pszPath[index] = PATH_BACKSLASH_CHR;
+ }
+#elif (PATH_SEPARATOR_CHR == PATH_SLASH_CHR)
+ /* Windows-style to Unix-style */
+
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_BACKSLASH_CHR)
+ pszPath[index] = PATH_SLASH_CHR;
+ }
+#else
+ /* Unexpected error */
+ return E_FAIL;
+#endif
+ }
+ else
+ {
+ /* Gangnam style? */
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+HRESULT PathCchConvertStyleW(PWSTR pszPath, size_t cchPath, unsigned long dwFlags)
+{
+ if (dwFlags == PATH_STYLE_WINDOWS)
+ {
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_SLASH_CHR)
+ pszPath[index] = PATH_BACKSLASH_CHR;
+ }
+ }
+ else if (dwFlags == PATH_STYLE_UNIX)
+ {
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_BACKSLASH_CHR)
+ pszPath[index] = PATH_SLASH_CHR;
+ }
+ }
+ else if (dwFlags == PATH_STYLE_NATIVE)
+ {
+#if (PATH_SEPARATOR_CHR == PATH_BACKSLASH_CHR)
+ {
+ /* Unix-style to Windows-style */
+
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_SLASH_CHR)
+ pszPath[index] = PATH_BACKSLASH_CHR;
+ }
+ }
+#elif (PATH_SEPARATOR_CHR == PATH_SLASH_CHR)
+ {
+ /* Windows-style to Unix-style */
+
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_BACKSLASH_CHR)
+ pszPath[index] = PATH_SLASH_CHR;
+ }
+ }
+#else
+ {
+ /* Unexpected error */
+ return E_FAIL;
+ }
+#endif
+ }
+ else
+ {
+ /* Gangnam style? */
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+/**
+ * PathGetSeparator
+ */
+
+char PathGetSeparatorA(unsigned long dwFlags)
+{
+ char separator = PATH_SEPARATOR_CHR;
+
+ if (!dwFlags)
+ dwFlags = PATH_STYLE_NATIVE;
+
+ if (dwFlags == PATH_STYLE_WINDOWS)
+ separator = PATH_SEPARATOR_CHR;
+ else if (dwFlags == PATH_STYLE_UNIX)
+ separator = PATH_SEPARATOR_CHR;
+ else if (dwFlags == PATH_STYLE_NATIVE)
+ separator = PATH_SEPARATOR_CHR;
+
+ return separator;
+}
+
+WCHAR PathGetSeparatorW(unsigned long dwFlags)
+{
+ union
+ {
+ WCHAR w;
+ char c[2];
+ } cnv;
+
+ cnv.c[0] = PATH_SEPARATOR_CHR;
+ cnv.c[1] = '\0';
+
+ if (!dwFlags)
+ dwFlags = PATH_STYLE_NATIVE;
+
+ if (dwFlags == PATH_STYLE_WINDOWS)
+ cnv.c[0] = PATH_SEPARATOR_CHR;
+ else if (dwFlags == PATH_STYLE_UNIX)
+ cnv.c[0] = PATH_SEPARATOR_CHR;
+ else if (dwFlags == PATH_STYLE_NATIVE)
+ cnv.c[0] = PATH_SEPARATOR_CHR;
+
+ return cnv.w;
+}
+
+/**
+ * PathGetSharedLibraryExtension
+ */
+static const CHAR SharedLibraryExtensionDllA[] = "dll";
+static const CHAR SharedLibraryExtensionSoA[] = "so";
+static const CHAR SharedLibraryExtensionDylibA[] = "dylib";
+
+static const CHAR SharedLibraryExtensionDotDllA[] = ".dll";
+static const CHAR SharedLibraryExtensionDotSoA[] = ".so";
+static const CHAR SharedLibraryExtensionDotDylibA[] = ".dylib";
+PCSTR PathGetSharedLibraryExtensionA(unsigned long dwFlags)
+{
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT)
+ {
+ if (dwFlags & PATH_SHARED_LIB_EXT_WITH_DOT)
+ {
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DLL)
+ return SharedLibraryExtensionDotDllA;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_SO)
+ return SharedLibraryExtensionDotSoA;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DYLIB)
+ return SharedLibraryExtensionDotDylibA;
+ }
+ else
+ {
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DLL)
+ return SharedLibraryExtensionDllA;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_SO)
+ return SharedLibraryExtensionSoA;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DYLIB)
+ return SharedLibraryExtensionDylibA;
+ }
+ }
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_WITH_DOT)
+ {
+#ifdef _WIN32
+ return SharedLibraryExtensionDotDllA;
+#elif defined(__APPLE__)
+ if (dwFlags & PATH_SHARED_LIB_EXT_APPLE_SO)
+ return SharedLibraryExtensionDotSoA;
+ else
+ return SharedLibraryExtensionDotDylibA;
+#else
+ return SharedLibraryExtensionDotSoA;
+#endif
+ }
+ else
+ {
+#ifdef _WIN32
+ return SharedLibraryExtensionDllA;
+#elif defined(__APPLE__)
+ if (dwFlags & PATH_SHARED_LIB_EXT_APPLE_SO)
+ return SharedLibraryExtensionSoA;
+ else
+ return SharedLibraryExtensionDylibA;
+#else
+ return SharedLibraryExtensionSoA;
+#endif
+ }
+
+ return NULL;
+}
+
+PCWSTR PathGetSharedLibraryExtensionW(unsigned long dwFlags)
+{
+ WCHAR buffer[6][16] = { 0 };
+ const WCHAR* SharedLibraryExtensionDotDllW = InitializeConstWCharFromUtf8(
+ SharedLibraryExtensionDotDllA, buffer[0], ARRAYSIZE(buffer[0]));
+ const WCHAR* SharedLibraryExtensionDotSoW =
+ InitializeConstWCharFromUtf8(SharedLibraryExtensionDotSoA, buffer[1], ARRAYSIZE(buffer[1]));
+ const WCHAR* SharedLibraryExtensionDotDylibW = InitializeConstWCharFromUtf8(
+ SharedLibraryExtensionDotDylibA, buffer[2], ARRAYSIZE(buffer[2]));
+ const WCHAR* SharedLibraryExtensionDllW =
+ InitializeConstWCharFromUtf8(SharedLibraryExtensionDllA, buffer[3], ARRAYSIZE(buffer[3]));
+ const WCHAR* SharedLibraryExtensionSoW =
+ InitializeConstWCharFromUtf8(SharedLibraryExtensionSoA, buffer[4], ARRAYSIZE(buffer[4]));
+ const WCHAR* SharedLibraryExtensionDylibW =
+ InitializeConstWCharFromUtf8(SharedLibraryExtensionDylibA, buffer[5], ARRAYSIZE(buffer[5]));
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT)
+ {
+ if (dwFlags & PATH_SHARED_LIB_EXT_WITH_DOT)
+ {
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DLL)
+ return SharedLibraryExtensionDotDllW;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_SO)
+ return SharedLibraryExtensionDotSoW;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DYLIB)
+ return SharedLibraryExtensionDotDylibW;
+ }
+ else
+ {
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DLL)
+ return SharedLibraryExtensionDllW;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_SO)
+ return SharedLibraryExtensionSoW;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DYLIB)
+ return SharedLibraryExtensionDylibW;
+ }
+ }
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_WITH_DOT)
+ {
+#ifdef _WIN32
+ return SharedLibraryExtensionDotDllW;
+#elif defined(__APPLE__)
+ if (dwFlags & PATH_SHARED_LIB_EXT_APPLE_SO)
+ return SharedLibraryExtensionDotSoW;
+ else
+ return SharedLibraryExtensionDotDylibW;
+#else
+ return SharedLibraryExtensionDotSoW;
+#endif
+ }
+ else
+ {
+#ifdef _WIN32
+ return SharedLibraryExtensionDllW;
+#elif defined(__APPLE__)
+ if (dwFlags & PATH_SHARED_LIB_EXT_APPLE_SO)
+ return SharedLibraryExtensionSoW;
+ else
+ return SharedLibraryExtensionDylibW;
+#else
+ return SharedLibraryExtensionSoW;
+#endif
+ }
+
+ return NULL;
+}
+
+const char* GetKnownPathIdString(int id)
+{
+ switch (id)
+ {
+ case KNOWN_PATH_HOME:
+ return "KNOWN_PATH_HOME";
+ case KNOWN_PATH_TEMP:
+ return "KNOWN_PATH_TEMP";
+ case KNOWN_PATH_XDG_DATA_HOME:
+ return "KNOWN_PATH_XDG_DATA_HOME";
+ case KNOWN_PATH_XDG_CONFIG_HOME:
+ return "KNOWN_PATH_XDG_CONFIG_HOME";
+ case KNOWN_PATH_XDG_CACHE_HOME:
+ return "KNOWN_PATH_XDG_CACHE_HOME";
+ case KNOWN_PATH_XDG_RUNTIME_DIR:
+ return "KNOWN_PATH_XDG_RUNTIME_DIR";
+ default:
+ return "KNOWN_PATH_UNKNOWN_ID";
+ }
+}
+
+static WCHAR* concat(const WCHAR* path, size_t pathlen, const WCHAR* name, size_t namelen)
+{
+ WCHAR* str = calloc(pathlen + namelen + 1, sizeof(WCHAR));
+ if (!str)
+ return NULL;
+
+ _wcsncat(str, path, pathlen);
+ _wcsncat(str, name, namelen);
+ return str;
+}
+
+BOOL winpr_RemoveDirectory_RecursiveA(LPCSTR lpPathName)
+{
+ WCHAR* name = ConvertUtf8ToWCharAlloc(lpPathName, NULL);
+ if (!name)
+ return FALSE;
+ const BOOL rc = winpr_RemoveDirectory_RecursiveW(name);
+ free(name);
+ return rc;
+}
+
+BOOL winpr_RemoveDirectory_RecursiveW(LPCWSTR lpPathName)
+{
+ BOOL ret = FALSE;
+
+ if (!lpPathName)
+ return FALSE;
+
+ const size_t pathnamelen = _wcslen(lpPathName);
+ const size_t path_slash_len = pathnamelen + 3;
+ WCHAR* path_slash = calloc(pathnamelen + 4, sizeof(WCHAR));
+ if (!path_slash)
+ return FALSE;
+ _wcsncat(path_slash, lpPathName, pathnamelen);
+
+ WCHAR starbuffer[8] = { 0 };
+ const WCHAR* star = InitializeConstWCharFromUtf8("*", starbuffer, ARRAYSIZE(starbuffer));
+ const HRESULT hr = NativePathCchAppendW(path_slash, path_slash_len, star);
+ if (FAILED(hr))
+ goto fail;
+
+ WIN32_FIND_DATAW findFileData = { 0 };
+ HANDLE dir = FindFirstFileW(path_slash, &findFileData);
+
+ if (dir == INVALID_HANDLE_VALUE)
+ goto fail;
+
+ ret = TRUE;
+ path_slash[path_slash_len - 1] = '\0'; /* remove trailing '*' */
+ do
+ {
+ const size_t len = _wcsnlen(findFileData.cFileName, ARRAYSIZE(findFileData.cFileName));
+
+ if ((len == 1 && findFileData.cFileName[0] == '.') ||
+ (len == 2 && findFileData.cFileName[0] == '.' && findFileData.cFileName[1] == '.'))
+ {
+ continue;
+ }
+
+ WCHAR* fullpath = concat(path_slash, path_slash_len, findFileData.cFileName, len);
+ if (!fullpath)
+ goto fail;
+
+ if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ ret = winpr_RemoveDirectory_RecursiveW(fullpath);
+ else
+ ret = DeleteFileW(fullpath);
+
+ free(fullpath);
+
+ if (!ret)
+ break;
+ } while (ret && FindNextFileW(dir, &findFileData) != 0);
+
+ FindClose(dir);
+
+ if (ret)
+ {
+ if (!RemoveDirectoryW(lpPathName))
+ ret = FALSE;
+ }
+
+fail:
+ free(path_slash);
+ return ret;
+}
diff --git a/winpr/libwinpr/path/shell.c b/winpr/libwinpr/path/shell.c
new file mode 100644
index 0000000..4380a9b
--- /dev/null
+++ b/winpr/libwinpr/path/shell.c
@@ -0,0 +1,821 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Path Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <winpr/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <winpr/crt.h>
+#include <winpr/platform.h>
+#include <winpr/file.h>
+#include <winpr/tchar.h>
+#include <winpr/environment.h>
+
+#include <winpr/path.h>
+#include <winpr/wlog.h>
+
+#include "../log.h"
+#define TAG WINPR_TAG("path.shell")
+
+#if defined(__IOS__)
+#include "shell_ios.h"
+#endif
+
+#if defined(WIN32)
+#include <shlobj.h>
+#else
+#include <errno.h>
+#include <dirent.h>
+#endif
+
+static char* GetPath_XDG_CONFIG_HOME(void);
+static char* GetPath_XDG_RUNTIME_DIR(void);
+
+/**
+ * SHGetKnownFolderPath function:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/bb762188/
+ */
+
+/**
+ * XDG Base Directory Specification:
+ * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+ */
+
+char* GetEnvAlloc(LPCSTR lpName)
+{
+ DWORD nSize = 0;
+ DWORD nStatus = 0;
+ char* env = NULL;
+
+ nSize = GetEnvironmentVariableX(lpName, NULL, 0);
+
+ if (nSize > 0)
+ {
+ env = malloc(nSize);
+
+ if (!env)
+ return NULL;
+
+ nStatus = GetEnvironmentVariableX(lpName, env, nSize);
+
+ if (nStatus != (nSize - 1))
+ {
+ free(env);
+ return NULL;
+ }
+ }
+
+ return env;
+}
+
+static char* GetPath_HOME(void)
+{
+ char* path = NULL;
+#ifdef _WIN32
+ path = GetEnvAlloc("UserProfile");
+#elif defined(__IOS__)
+ path = ios_get_home();
+#else
+ path = GetEnvAlloc("HOME");
+#endif
+ return path;
+}
+
+static char* GetPath_TEMP(void)
+{
+ char* path = NULL;
+#ifdef _WIN32
+ path = GetEnvAlloc("TEMP");
+#elif defined(__IOS__)
+ path = ios_get_temp();
+#else
+ path = GetEnvAlloc("TMPDIR");
+
+ if (!path)
+ path = _strdup("/tmp");
+
+#endif
+ return path;
+}
+
+static char* GetPath_XDG_DATA_HOME(void)
+{
+ char* path = NULL;
+#if defined(WIN32) || defined(__IOS__)
+ path = GetPath_XDG_CONFIG_HOME();
+#else
+ size_t size = 0;
+ char* home = NULL;
+ /**
+ * There is a single base directory relative to which user-specific data files should be
+ * written. This directory is defined by the environment variable $XDG_DATA_HOME.
+ *
+ * $XDG_DATA_HOME defines the base directory relative to which user specific data files should
+ * be stored. If $XDG_DATA_HOME is either not set or empty, a default equal to
+ * $HOME/.local/share should be used.
+ */
+ path = GetEnvAlloc("XDG_DATA_HOME");
+
+ if (path)
+ return path;
+
+ home = GetPath_HOME();
+
+ if (!home)
+ return NULL;
+
+ size = strlen(home) + strlen("/.local/share") + 1;
+ path = (char*)malloc(size);
+
+ if (!path)
+ {
+ free(home);
+ return NULL;
+ }
+
+ sprintf_s(path, size, "%s%s", home, "/.local/share");
+ free(home);
+#endif
+ return path;
+}
+
+static char* GetPath_XDG_CONFIG_HOME(void)
+{
+ char* path = NULL;
+#if defined(WIN32) && !defined(_UWP)
+ path = calloc(MAX_PATH, sizeof(char));
+
+ if (!path)
+ return NULL;
+
+ if (FAILED(SHGetFolderPathA(0, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path)))
+ {
+ free(path);
+ return NULL;
+ }
+
+#elif defined(__IOS__)
+ path = ios_get_data();
+#else
+ size_t size = 0;
+ char* home = NULL;
+ /**
+ * There is a single base directory relative to which user-specific configuration files should
+ * be written. This directory is defined by the environment variable $XDG_CONFIG_HOME.
+ *
+ * $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration
+ * files should be stored. If $XDG_CONFIG_HOME is either not set or empty, a default equal to
+ * $HOME/.config should be used.
+ */
+ path = GetEnvAlloc("XDG_CONFIG_HOME");
+
+ if (path)
+ return path;
+
+ home = GetPath_HOME();
+
+ if (!home)
+ home = GetPath_TEMP();
+
+ if (!home)
+ return NULL;
+
+ size = strlen(home) + strlen("/.config") + 1;
+ path = (char*)malloc(size);
+
+ if (!path)
+ {
+ free(home);
+ return NULL;
+ }
+
+ sprintf_s(path, size, "%s%s", home, "/.config");
+ free(home);
+#endif
+ return path;
+}
+
+static char* GetPath_XDG_CACHE_HOME(void)
+{
+ char* path = NULL;
+ char* home = NULL;
+#if defined(WIN32)
+ home = GetPath_XDG_RUNTIME_DIR();
+
+ if (home)
+ {
+ path = GetCombinedPath(home, "cache");
+
+ if (!winpr_PathFileExists(path))
+ if (!CreateDirectoryA(path, NULL))
+ path = NULL;
+ }
+
+ free(home);
+#elif defined(__IOS__)
+ path = ios_get_cache();
+#else
+ size_t size = 0;
+ /**
+ * There is a single base directory relative to which user-specific non-essential (cached) data
+ * should be written. This directory is defined by the environment variable $XDG_CACHE_HOME.
+ *
+ * $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data
+ * files should be stored. If $XDG_CACHE_HOME is either not set or empty, a default equal to
+ * $HOME/.cache should be used.
+ */
+ path = GetEnvAlloc("XDG_CACHE_HOME");
+
+ if (path)
+ return path;
+
+ home = GetPath_HOME();
+
+ if (!home)
+ return NULL;
+
+ size = strlen(home) + strlen("/.cache") + 1;
+ path = (char*)malloc(size);
+
+ if (!path)
+ {
+ free(home);
+ return NULL;
+ }
+
+ sprintf_s(path, size, "%s%s", home, "/.cache");
+ free(home);
+#endif
+ return path;
+}
+
+char* GetPath_XDG_RUNTIME_DIR(void)
+{
+ char* path = NULL;
+#if defined(WIN32) && !defined(_UWP)
+ path = calloc(MAX_PATH, sizeof(char));
+
+ if (!path)
+ return NULL;
+
+ if (FAILED(SHGetFolderPathA(0, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path)))
+ {
+ free(path);
+ return NULL;
+ }
+
+#else
+ /**
+ * There is a single base directory relative to which user-specific runtime files and other file
+ * objects should be placed. This directory is defined by the environment variable
+ * $XDG_RUNTIME_DIR.
+ *
+ * $XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential
+ * runtime files and other file objects (such as sockets, named pipes, ...) should be stored.
+ * The directory MUST be owned by the user, and he MUST be the only one having read and write
+ * access to it. Its Unix access mode MUST be 0700.
+ *
+ * The lifetime of the directory MUST be bound to the user being logged in. It MUST be created
+ * when the user first logs in and if the user fully logs out the directory MUST be removed. If
+ * the user logs in more than once he should get pointed to the same directory, and it is
+ * mandatory that the directory continues to exist from his first login to his last logout on
+ * the system, and not removed in between. Files in the directory MUST not survive reboot or a
+ * full logout/login cycle.
+ *
+ * The directory MUST be on a local file system and not shared with any other system. The
+ * directory MUST by fully-featured by the standards of the operating system. More specifically,
+ * on Unix-like operating systems AF_UNIX sockets, symbolic links, hard links, proper
+ * permissions, file locking, sparse files, memory mapping, file change notifications, a
+ * reliable hard link count must be supported, and no restrictions on the file name character
+ * set should be imposed. Files in this directory MAY be subjected to periodic clean-up. To
+ * ensure that your files are not removed, they should have their access time timestamp modified
+ * at least once every 6 hours of monotonic time or the 'sticky' bit should be set on the file.
+ *
+ * If $XDG_RUNTIME_DIR is not set applications should fall back to a replacement directory with
+ * similar capabilities and print a warning message. Applications should use this directory for
+ * communication and synchronization purposes and should not place larger files in it, since it
+ * might reside in runtime memory and cannot necessarily be swapped out to disk.
+ */
+ path = GetEnvAlloc("XDG_RUNTIME_DIR");
+#endif
+
+ if (path)
+ return path;
+
+ path = GetPath_TEMP();
+ return path;
+}
+
+char* GetKnownPath(int id)
+{
+ char* path = NULL;
+
+ switch (id)
+ {
+ case KNOWN_PATH_HOME:
+ path = GetPath_HOME();
+ break;
+
+ case KNOWN_PATH_TEMP:
+ path = GetPath_TEMP();
+ break;
+
+ case KNOWN_PATH_XDG_DATA_HOME:
+ path = GetPath_XDG_DATA_HOME();
+ break;
+
+ case KNOWN_PATH_XDG_CONFIG_HOME:
+ path = GetPath_XDG_CONFIG_HOME();
+ break;
+
+ case KNOWN_PATH_XDG_CACHE_HOME:
+ path = GetPath_XDG_CACHE_HOME();
+ break;
+
+ case KNOWN_PATH_XDG_RUNTIME_DIR:
+ path = GetPath_XDG_RUNTIME_DIR();
+ break;
+
+ default:
+ path = NULL;
+ break;
+ }
+
+ if (!path)
+ WLog_WARN(TAG, "Path %s is %p", GetKnownPathIdString(id), path);
+ return path;
+}
+
+char* GetKnownSubPath(int id, const char* path)
+{
+ char* subPath = NULL;
+ char* knownPath = NULL;
+ knownPath = GetKnownPath(id);
+
+ if (!knownPath)
+ return NULL;
+
+ subPath = GetCombinedPath(knownPath, path);
+ free(knownPath);
+ return subPath;
+}
+
+char* GetEnvironmentPath(char* name)
+{
+ char* env = NULL;
+ DWORD nSize = 0;
+ DWORD nStatus = 0;
+ nSize = GetEnvironmentVariableX(name, NULL, 0);
+
+ if (nSize)
+ {
+ env = (LPSTR)malloc(nSize);
+
+ if (!env)
+ return NULL;
+
+ nStatus = GetEnvironmentVariableX(name, env, nSize);
+
+ if (nStatus != (nSize - 1))
+ {
+ free(env);
+ return NULL;
+ }
+ }
+
+ return env;
+}
+
+char* GetEnvironmentSubPath(char* name, const char* path)
+{
+ char* env = NULL;
+ char* subpath = NULL;
+ env = GetEnvironmentPath(name);
+
+ if (!env)
+ return NULL;
+
+ subpath = GetCombinedPath(env, path);
+ free(env);
+ return subpath;
+}
+
+char* GetCombinedPath(const char* basePath, const char* subPath)
+{
+ size_t length = 0;
+ HRESULT status = 0;
+ char* path = NULL;
+ char* subPathCpy = NULL;
+ size_t basePathLength = 0;
+ size_t subPathLength = 0;
+
+ if (basePath)
+ basePathLength = strlen(basePath);
+
+ if (subPath)
+ subPathLength = strlen(subPath);
+
+ length = basePathLength + subPathLength + 1;
+ path = (char*)calloc(1, length + 1);
+
+ if (!path)
+ goto fail;
+
+ if (basePath)
+ CopyMemory(path, basePath, basePathLength);
+
+ if (FAILED(PathCchConvertStyleA(path, basePathLength, PATH_STYLE_NATIVE)))
+ goto fail;
+
+ if (!subPath)
+ return path;
+
+ subPathCpy = _strdup(subPath);
+
+ if (!subPathCpy)
+ goto fail;
+
+ if (FAILED(PathCchConvertStyleA(subPathCpy, subPathLength, PATH_STYLE_NATIVE)))
+ goto fail;
+
+ status = NativePathCchAppendA(path, length + 1, subPathCpy);
+ if (FAILED(status))
+ goto fail;
+
+ free(subPathCpy);
+ return path;
+
+fail:
+ free(path);
+ free(subPathCpy);
+ return NULL;
+}
+
+BOOL PathMakePathA(LPCSTR path, LPSECURITY_ATTRIBUTES lpAttributes)
+{
+#if defined(_UWP)
+ return FALSE;
+#elif defined(_WIN32)
+ return (SHCreateDirectoryExA(NULL, path, lpAttributes) == ERROR_SUCCESS);
+#else
+ const char delim = PathGetSeparatorA(PATH_STYLE_NATIVE);
+ char* dup = NULL;
+ BOOL result = TRUE;
+ /* we only operate on a non-null, absolute path */
+#if defined(__OS2__)
+
+ if (!path)
+ return FALSE;
+
+#else
+
+ if (!path || *path != delim)
+ return FALSE;
+
+#endif
+
+ if (!(dup = _strdup(path)))
+ return FALSE;
+
+#ifdef __OS2__
+ p = (strlen(dup) > 3) && (dup[1] == ':') && (dup[2] == delim)) ? &dup[3] : dup;
+
+ while (p)
+#else
+ for (char* p = dup; p;)
+#endif
+ {
+ if ((p = strchr(p + 1, delim)))
+ *p = '\0';
+
+ if (mkdir(dup, 0777) != 0)
+ if (errno != EEXIST)
+ {
+ result = FALSE;
+ break;
+ }
+
+ if (p)
+ *p = delim;
+ }
+
+ free(dup);
+ return (result);
+#endif
+}
+
+BOOL PathMakePathW(LPCWSTR path, LPSECURITY_ATTRIBUTES lpAttributes)
+{
+#if defined(_UWP)
+ return FALSE;
+#elif defined(_WIN32)
+ return (SHCreateDirectoryExW(NULL, path, lpAttributes) == ERROR_SUCCESS);
+#else
+ const WCHAR wdelim = PathGetSeparatorW(PATH_STYLE_NATIVE);
+ const char delim = PathGetSeparatorA(PATH_STYLE_NATIVE);
+ char* dup = NULL;
+ BOOL result = TRUE;
+ /* we only operate on a non-null, absolute path */
+#if defined(__OS2__)
+
+ if (!path)
+ return FALSE;
+
+#else
+
+ if (!path || *path != wdelim)
+ return FALSE;
+
+#endif
+
+ dup = ConvertWCharToUtf8Alloc(path, NULL);
+ if (!dup)
+ return FALSE;
+
+#ifdef __OS2__
+ p = (strlen(dup) > 3) && (dup[1] == ':') && (dup[2] == delim)) ? &dup[3] : dup;
+
+ while (p)
+#else
+ for (char* p = dup; p;)
+#endif
+ {
+ if ((p = strchr(p + 1, delim)))
+ *p = '\0';
+
+ if (mkdir(dup, 0777) != 0)
+ if (errno != EEXIST)
+ {
+ result = FALSE;
+ break;
+ }
+
+ if (p)
+ *p = delim;
+ }
+
+ free(dup);
+ return (result);
+#endif
+}
+
+#if !defined(_WIN32) || defined(_UWP)
+
+BOOL PathIsRelativeA(LPCSTR pszPath)
+{
+ if (!pszPath)
+ return FALSE;
+
+ return pszPath[0] != '/';
+}
+
+BOOL PathIsRelativeW(LPCWSTR pszPath)
+{
+ LPSTR lpFileNameA = NULL;
+ BOOL ret = FALSE;
+
+ if (!pszPath)
+ goto fail;
+
+ lpFileNameA = ConvertWCharToUtf8Alloc(pszPath, NULL);
+ if (!lpFileNameA)
+ goto fail;
+ ret = PathIsRelativeA(lpFileNameA);
+fail:
+ free(lpFileNameA);
+ return ret;
+}
+
+BOOL PathFileExistsA(LPCSTR pszPath)
+{
+ struct stat stat_info;
+
+ if (stat(pszPath, &stat_info) != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL PathFileExistsW(LPCWSTR pszPath)
+{
+ LPSTR lpFileNameA = NULL;
+ BOOL ret = FALSE;
+
+ if (!pszPath)
+ goto fail;
+ lpFileNameA = ConvertWCharToUtf8Alloc(pszPath, NULL);
+ if (!lpFileNameA)
+ goto fail;
+
+ ret = winpr_PathFileExists(lpFileNameA);
+fail:
+ free(lpFileNameA);
+ return ret;
+}
+
+BOOL PathIsDirectoryEmptyA(LPCSTR pszPath)
+{
+ struct dirent* dp = NULL;
+ int empty = 1;
+ DIR* dir = opendir(pszPath);
+
+ if (dir == NULL) /* Not a directory or doesn't exist */
+ return 1;
+
+ while ((dp = readdir(dir)) != NULL)
+ {
+ if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
+ continue; /* Skip . and .. */
+
+ empty = 0;
+ break;
+ }
+
+ closedir(dir);
+ return empty;
+}
+
+BOOL PathIsDirectoryEmptyW(LPCWSTR pszPath)
+{
+ LPSTR lpFileNameA = NULL;
+ BOOL ret = FALSE;
+ if (!pszPath)
+ goto fail;
+ lpFileNameA = ConvertWCharToUtf8Alloc(pszPath, NULL);
+ if (!lpFileNameA)
+ goto fail;
+ ret = PathIsDirectoryEmptyA(lpFileNameA);
+fail:
+ free(lpFileNameA);
+ return ret;
+}
+
+#else
+
+#ifdef _MSC_VER
+#pragma comment(lib, "shlwapi.lib")
+#endif
+
+#endif
+
+BOOL winpr_MoveFile(LPCSTR lpExistingFileName, LPCSTR lpNewFileName)
+{
+#ifndef _WIN32
+ return MoveFileA(lpExistingFileName, lpNewFileName);
+#else
+ BOOL result = FALSE;
+ LPWSTR lpExistingFileNameW = NULL;
+ LPWSTR lpNewFileNameW = NULL;
+
+ if (!lpExistingFileName || !lpNewFileName)
+ return FALSE;
+
+ lpExistingFileNameW = ConvertUtf8ToWCharAlloc(lpExistingFileName, NULL);
+ if (!lpExistingFileNameW)
+ goto cleanup;
+ lpNewFileNameW = ConvertUtf8ToWCharAlloc(lpNewFileName, NULL);
+ if (!lpNewFileNameW)
+ goto cleanup;
+
+ result = MoveFileW(lpExistingFileNameW, lpNewFileNameW);
+
+cleanup:
+ free(lpExistingFileNameW);
+ free(lpNewFileNameW);
+ return result;
+#endif
+}
+
+BOOL winpr_MoveFileEx(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, DWORD dwFlags)
+{
+#ifndef _WIN32
+ return MoveFileExA(lpExistingFileName, lpNewFileName, dwFlags);
+#else
+ BOOL result = FALSE;
+ LPWSTR lpExistingFileNameW = NULL;
+ LPWSTR lpNewFileNameW = NULL;
+
+ if (!lpExistingFileName || !lpNewFileName)
+ return FALSE;
+
+ lpExistingFileNameW = ConvertUtf8ToWCharAlloc(lpExistingFileName, NULL);
+ if (!lpExistingFileNameW)
+ goto cleanup;
+ lpNewFileNameW = ConvertUtf8ToWCharAlloc(lpNewFileName, NULL);
+ if (!lpNewFileNameW)
+ goto cleanup;
+
+ result = MoveFileExW(lpExistingFileNameW, lpNewFileNameW, dwFlags);
+
+cleanup:
+ free(lpExistingFileNameW);
+ free(lpNewFileNameW);
+ return result;
+#endif
+}
+
+BOOL winpr_DeleteFile(const char* lpFileName)
+{
+#ifndef _WIN32
+ return DeleteFileA(lpFileName);
+#else
+ LPWSTR lpFileNameW = NULL;
+ BOOL result = FALSE;
+
+ if (lpFileName)
+ {
+ lpFileNameW = ConvertUtf8ToWCharAlloc(lpFileName, NULL);
+ if (!lpFileNameW)
+ goto cleanup;
+ }
+
+ result = DeleteFileW(lpFileNameW);
+
+cleanup:
+ free(lpFileNameW);
+ return result;
+#endif
+}
+
+BOOL winpr_RemoveDirectory(LPCSTR lpPathName)
+{
+#ifndef _WIN32
+ return RemoveDirectoryA(lpPathName);
+#else
+ LPWSTR lpPathNameW = NULL;
+ BOOL result = FALSE;
+
+ if (lpPathName)
+ {
+ lpPathNameW = ConvertUtf8ToWCharAlloc(lpPathName, NULL);
+ if (!lpPathNameW)
+ goto cleanup;
+ }
+
+ result = RemoveDirectoryW(lpPathNameW);
+
+cleanup:
+ free(lpPathNameW);
+ return result;
+#endif
+}
+
+BOOL winpr_PathFileExists(const char* pszPath)
+{
+ if (!pszPath)
+ return FALSE;
+#ifndef _WIN32
+ return PathFileExistsA(pszPath);
+#else
+ WCHAR* pathW = ConvertUtf8ToWCharAlloc(pszPath, NULL);
+ BOOL result = FALSE;
+
+ if (!pathW)
+ return FALSE;
+
+ result = PathFileExistsW(pathW);
+ free(pathW);
+
+ return result;
+#endif
+}
+
+BOOL winpr_PathMakePath(const char* path, LPSECURITY_ATTRIBUTES lpAttributes)
+{
+ if (!path)
+ return FALSE;
+#ifndef _WIN32
+ return PathMakePathA(path, lpAttributes);
+#else
+ WCHAR* pathW = ConvertUtf8ToWCharAlloc(path, NULL);
+ BOOL result = FALSE;
+
+ if (!pathW)
+ return FALSE;
+
+ result = SHCreateDirectoryExW(NULL, pathW, lpAttributes) == ERROR_SUCCESS;
+ free(pathW);
+
+ return result;
+#endif
+}
diff --git a/winpr/libwinpr/path/shell_ios.h b/winpr/libwinpr/path/shell_ios.h
new file mode 100644
index 0000000..3144d8d
--- /dev/null
+++ b/winpr/libwinpr/path/shell_ios.h
@@ -0,0 +1,9 @@
+#ifndef SHELL_IOS_H_
+#define SHELL_IOS_H_
+
+char* ios_get_home(void);
+char* ios_get_temp(void);
+char* ios_get_data(void);
+char* ios_get_cache(void);
+
+#endif
diff --git a/winpr/libwinpr/path/shell_ios.m b/winpr/libwinpr/path/shell_ios.m
new file mode 100644
index 0000000..7e1185b
--- /dev/null
+++ b/winpr/libwinpr/path/shell_ios.m
@@ -0,0 +1,54 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Path Functions
+ *
+ * Copyright 2016 Armin Novak <armin.novak@thincast.om>
+ * Copyright 2016 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+#include <winpr/config.h>
+
+#include "shell_ios.h"
+
+NSString *ios_get_directory_for_search_path(NSSearchPathDirectory searchPath)
+{
+ return [NSSearchPathForDirectoriesInDomains(searchPath, NSUserDomainMask, YES) lastObject];
+}
+
+char *ios_get_home(void)
+{
+ NSString *path = ios_get_directory_for_search_path(NSDocumentDirectory);
+ return strdup([path UTF8String]);
+}
+
+char *ios_get_temp(void)
+{
+ NSString *tmp_path = NSTemporaryDirectory();
+ return strdup([tmp_path UTF8String]);
+}
+
+char *ios_get_data(void)
+{
+ NSString *path = ios_get_directory_for_search_path(NSApplicationSupportDirectory);
+ return strdup([path UTF8String]);
+}
+
+char *ios_get_cache(void)
+{
+ NSString *path = ios_get_directory_for_search_path(NSCachesDirectory);
+ return strdup([path UTF8String]);
+}
diff --git a/winpr/libwinpr/path/test/CMakeLists.txt b/winpr/libwinpr/path/test/CMakeLists.txt
new file mode 100644
index 0000000..974907e
--- /dev/null
+++ b/winpr/libwinpr/path/test/CMakeLists.txt
@@ -0,0 +1,48 @@
+
+set(MODULE_NAME "TestPath")
+set(MODULE_PREFIX "TEST_PATH")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestPathCchAddBackslash.c
+ TestPathCchRemoveBackslash.c
+ TestPathCchAddBackslashEx.c
+ TestPathCchRemoveBackslashEx.c
+ TestPathCchAddExtension.c
+ TestPathCchAppend.c
+ TestPathCchAppendEx.c
+ TestPathCchCanonicalize.c
+ TestPathCchCanonicalizeEx.c
+ TestPathAllocCanonicalize.c
+ TestPathCchCombine.c
+ TestPathCchCombineEx.c
+ TestPathAllocCombine.c
+ TestPathCchFindExtension.c
+ TestPathCchRenameExtension.c
+ TestPathCchRemoveExtension.c
+ TestPathCchIsRoot.c
+ TestPathIsUNCEx.c
+ TestPathCchSkipRoot.c
+ TestPathCchStripToRoot.c
+ TestPathCchStripPrefix.c
+ TestPathCchRemoveFileSpec.c
+ TestPathShell.c
+ TestPathMakePath.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
diff --git a/winpr/libwinpr/path/test/TestPathAllocCanonicalize.c b/winpr/libwinpr/path/test/TestPathAllocCanonicalize.c
new file mode 100644
index 0000000..d04fff1
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathAllocCanonicalize.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathAllocCanonicalize(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathAllocCombine.c b/winpr/libwinpr/path/test/TestPathAllocCombine.c
new file mode 100644
index 0000000..4630df0
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathAllocCombine.c
@@ -0,0 +1,98 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+static const TCHAR testBasePathBackslash[] = _T("C:\\Program Files\\");
+static const TCHAR testBasePathNoBackslash[] = _T("C:\\Program Files");
+static const TCHAR testMorePathBackslash[] = _T("\\Microsoft Visual Studio 11.0");
+static const TCHAR testMorePathNoBackslash[] = _T("Microsoft Visual Studio 11.0");
+static const TCHAR testPathOut[] = _T("C:\\Program Files\\Microsoft Visual Studio 11.0");
+static const TCHAR testPathOutMorePathBackslash[] = _T("C:\\Microsoft Visual Studio 11.0");
+
+int TestPathAllocCombine(int argc, char* argv[])
+{
+ HRESULT status = 0;
+ LPTSTR PathOut = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* Base Path: Backslash, More Path: No Backslash */
+
+ status = PathAllocCombine(testBasePathBackslash, testMorePathNoBackslash, 0, &PathOut);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathAllocCombine status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(PathOut, testPathOut) != 0)
+ {
+ _tprintf(_T("Path Mismatch 1: Actual: %s, Expected: %s\n"), PathOut, testPathOut);
+ return -1;
+ }
+
+ free(PathOut);
+
+ /* Base Path: Backslash, More Path: Backslash */
+
+ status = PathAllocCombine(testBasePathBackslash, testMorePathBackslash, 0, &PathOut);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathAllocCombine status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(PathOut, testPathOutMorePathBackslash) != 0)
+ {
+ _tprintf(_T("Path Mismatch 2: Actual: %s, Expected: %s\n"), PathOut,
+ testPathOutMorePathBackslash);
+ return -1;
+ }
+
+ free(PathOut);
+
+ /* Base Path: No Backslash, More Path: Backslash */
+
+ status = PathAllocCombine(testBasePathNoBackslash, testMorePathBackslash, 0, &PathOut);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathAllocCombine status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(PathOut, testPathOutMorePathBackslash) != 0)
+ {
+ _tprintf(_T("Path Mismatch 3: Actual: %s, Expected: %s\n"), PathOut,
+ testPathOutMorePathBackslash);
+ return -1;
+ }
+
+ free(PathOut);
+
+ /* Base Path: No Backslash, More Path: No Backslash */
+
+ status = PathAllocCombine(testBasePathNoBackslash, testMorePathNoBackslash, 0, &PathOut);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathAllocCombine status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(PathOut, testPathOut) != 0)
+ {
+ _tprintf(_T("Path Mismatch 4: Actual: %s, Expected: %s\n"), PathOut, testPathOut);
+ return -1;
+ }
+
+ free(PathOut);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchAddBackslash.c b/winpr/libwinpr/path/test/TestPathCchAddBackslash.c
new file mode 100644
index 0000000..0a414e2
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchAddBackslash.c
@@ -0,0 +1,100 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+static const TCHAR testPathBackslash[] = _T("C:\\Program Files\\");
+static const TCHAR testPathNoBackslash[] = _T("C:\\Program Files");
+
+int TestPathCchAddBackslash(int argc, char* argv[])
+{
+ HRESULT status = 0;
+ TCHAR Path[PATHCCH_MAX_CCH];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /**
+ * PathCchAddBackslash returns S_OK if the function was successful,
+ * S_FALSE if the path string already ends in a backslash,
+ * or an error code otherwise.
+ */
+
+ _tcscpy(Path, testPathNoBackslash);
+
+ /* Add a backslash to a path without a trailing backslash, expect S_OK */
+
+ status = PathCchAddBackslash(Path, PATHCCH_MAX_CCH);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAddBackslash status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathBackslash) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathBackslash);
+ return -1;
+ }
+
+ /* Add a backslash to a path with a trailing backslash, expect S_FALSE */
+
+ _tcscpy(Path, testPathBackslash);
+
+ status = PathCchAddBackslash(Path, PATHCCH_MAX_CCH);
+
+ if (status != S_FALSE)
+ {
+ _tprintf(_T("PathCchAddBackslash status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathBackslash) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathBackslash);
+ return -1;
+ }
+
+ /* Use NULL PSTR, expect FAILED(status) */
+
+ status = PathCchAddBackslash(NULL, PATHCCH_MAX_CCH);
+
+ if (SUCCEEDED(status))
+ {
+ _tprintf(_T("PathCchAddBackslash unexpectedly succeded with null buffer. Status: 0x%08") _T(
+ PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* Use insufficient size value, expect FAILED(status) */
+
+ _tcscpy(Path, _T("C:\\tmp"));
+
+ status = PathCchAddBackslash(Path, 7);
+
+ if (SUCCEEDED(status))
+ {
+ _tprintf(_T("PathCchAddBackslash unexpectedly succeded with insufficient buffer size. ")
+ _T("Status: 0x%08") _T(PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* Use minimum required size value, expect S_OK */
+
+ _tcscpy(Path, _T("C:\\tmp"));
+
+ status = PathCchAddBackslash(Path, 8);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAddBackslash failed with status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchAddBackslashEx.c b/winpr/libwinpr/path/test/TestPathCchAddBackslashEx.c
new file mode 100644
index 0000000..4a84200
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchAddBackslashEx.c
@@ -0,0 +1,103 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+static const TCHAR testPathBackslash[] = _T("C:\\Program Files\\");
+static const TCHAR testPathNoBackslash[] = _T("C:\\Program Files");
+
+int TestPathCchAddBackslashEx(int argc, char* argv[])
+{
+ HRESULT status = 0;
+ LPTSTR pszEnd = NULL;
+ size_t cchRemaining = 0;
+ TCHAR Path[PATHCCH_MAX_CCH];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /**
+ * PathCchAddBackslashEx returns S_OK if the function was successful,
+ * S_FALSE if the path string already ends in a backslash,
+ * or an error code otherwise.
+ */
+
+ _tcscpy(Path, testPathNoBackslash);
+
+ /* Add a backslash to a path without a trailing backslash, expect S_OK */
+
+ status = PathCchAddBackslashEx(Path, sizeof(Path) / sizeof(TCHAR), &pszEnd, &cchRemaining);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAddBackslash status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathBackslash) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathBackslash);
+ return -1;
+ }
+
+ /* Add a backslash to a path with a trailing backslash, expect S_FALSE */
+
+ _tcscpy(Path, testPathBackslash);
+
+ status = PathCchAddBackslashEx(Path, sizeof(Path) / sizeof(TCHAR), &pszEnd, &cchRemaining);
+
+ if (status != S_FALSE)
+ {
+ _tprintf(_T("PathCchAddBackslash status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathBackslash) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathBackslash);
+ return -1;
+ }
+
+ /* Use NULL PSTR, expect FAILED(status) */
+
+ status = PathCchAddBackslashEx(NULL, PATHCCH_MAX_CCH, NULL, NULL);
+
+ if (SUCCEEDED(status))
+ {
+ _tprintf(
+ _T("PathCchAddBackslashEx unexpectedly succeded with null buffer. Status: 0x%08") _T(
+ PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* Use insufficient size value, expect FAILED(status) */
+
+ _tcscpy(Path, _T("C:\\tmp"));
+
+ status = PathCchAddBackslashEx(Path, 7, NULL, NULL);
+
+ if (SUCCEEDED(status))
+ {
+ _tprintf(_T("PathCchAddBackslashEx unexpectedly succeded with insufficient buffer size. ")
+ _T("Status: 0x%08") _T(PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* Use minimum required size value, expect S_OK */
+
+ _tcscpy(Path, _T("C:\\tmp"));
+
+ status = PathCchAddBackslashEx(Path, 8, NULL, NULL);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAddBackslashEx failed with status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchAddExtension.c b/winpr/libwinpr/path/test/TestPathCchAddExtension.c
new file mode 100644
index 0000000..71f5ddf
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchAddExtension.c
@@ -0,0 +1,140 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+static const TCHAR testExtDot[] = _T(".exe");
+static const TCHAR testExtNoDot[] = _T("exe");
+static const TCHAR testPathNoExtension[] = _T("C:\\Windows\\System32\\cmd");
+static const TCHAR testPathExtension[] = _T("C:\\Windows\\System32\\cmd.exe");
+
+int TestPathCchAddExtension(int argc, char* argv[])
+{
+ HRESULT status = 0;
+ TCHAR Path[PATHCCH_MAX_CCH];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* Path: no extension, Extension: dot */
+
+ _tcscpy(Path, testPathNoExtension);
+
+ status = PathCchAddExtension(Path, PATHCCH_MAX_CCH, testExtDot);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAddExtension status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathExtension) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathExtension);
+ return -1;
+ }
+
+ /* Path: no extension, Extension: no dot */
+
+ _tcscpy(Path, testPathNoExtension);
+
+ status = PathCchAddExtension(Path, PATHCCH_MAX_CCH, testExtNoDot);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAddExtension status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathExtension) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathExtension);
+ return -1;
+ }
+
+ /* Path: extension, Extension: dot */
+
+ _tcscpy(Path, testPathExtension);
+
+ status = PathCchAddExtension(Path, PATHCCH_MAX_CCH, testExtDot);
+
+ if (status != S_FALSE)
+ {
+ _tprintf(_T("PathCchAddExtension status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathExtension) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathExtension);
+ return -1;
+ }
+
+ /* Path: extension, Extension: no dot */
+
+ _tcscpy(Path, testPathExtension);
+
+ status = PathCchAddExtension(Path, PATHCCH_MAX_CCH, testExtDot);
+
+ if (status != S_FALSE)
+ {
+ _tprintf(_T("PathCchAddExtension status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathExtension) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathExtension);
+ return -1;
+ }
+
+ /* Path: NULL */
+
+ status = PathCchAddExtension(NULL, PATHCCH_MAX_CCH, testExtDot);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(_T("PathCchAddExtension with null buffer returned status: 0x%08") _T(
+ PRIX32) _T(" (expected E_INVALIDARG)\n"),
+ status);
+ return -1;
+ }
+
+ /* Extension: NULL */
+
+ status = PathCchAddExtension(Path, PATHCCH_MAX_CCH, NULL);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(_T("PathCchAddExtension with null extension returned status: 0x%08") _T(
+ PRIX32) _T(" (expected E_INVALIDARG)\n"),
+ status);
+ return -1;
+ }
+
+ /* Insufficient Buffer size */
+
+ _tcscpy(Path, _T("C:\\456789"));
+ status = PathCchAddExtension(Path, 9 + 4, _T(".jpg"));
+ if (SUCCEEDED(status))
+ {
+ _tprintf(_T("PathCchAddExtension with insufficient buffer unexpectedly succeeded with ")
+ _T("status: 0x%08") _T(PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* Minimum required buffer size */
+
+ _tcscpy(Path, _T("C:\\456789"));
+ status = PathCchAddExtension(Path, 9 + 4 + 1, _T(".jpg"));
+ if (FAILED(status))
+ {
+ _tprintf(_T("PathCchAddExtension with sufficient buffer unexpectedly failed with status: ")
+ _T("0x%08") _T(PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchAppend.c b/winpr/libwinpr/path/test/TestPathCchAppend.c
new file mode 100644
index 0000000..93524ca
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchAppend.c
@@ -0,0 +1,151 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+static const TCHAR testBasePathBackslash[] = _T("C:\\Program Files\\");
+static const TCHAR testBasePathNoBackslash[] = _T("C:\\Program Files");
+static const TCHAR testMorePathBackslash[] = _T("\\Microsoft Visual Studio 11.0");
+static const TCHAR testMorePathNoBackslash[] = _T("Microsoft Visual Studio 11.0");
+static const TCHAR testPathOut[] = _T("C:\\Program Files\\Microsoft Visual Studio 11.0");
+
+int TestPathCchAppend(int argc, char* argv[])
+{
+ HRESULT status = 0;
+ TCHAR Path[PATHCCH_MAX_CCH];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* Base Path: Backslash, More Path: No Backslash */
+
+ _tcscpy(Path, testBasePathBackslash);
+
+ status = PathCchAppend(Path, PATHCCH_MAX_CCH, testMorePathNoBackslash);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAppend status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathOut) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathOut);
+ return -1;
+ }
+
+ /* Base Path: Backslash, More Path: Backslash */
+
+ _tcscpy(Path, testBasePathBackslash);
+
+ status = PathCchAppend(Path, PATHCCH_MAX_CCH, testMorePathBackslash);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAppend status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathOut) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathOut);
+ return -1;
+ }
+
+ /* Base Path: No Backslash, More Path: Backslash */
+
+ _tcscpy(Path, testBasePathNoBackslash);
+
+ status = PathCchAppend(Path, PATHCCH_MAX_CCH, testMorePathBackslash);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAppend status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathOut) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathOut);
+ return -1;
+ }
+
+ /* Base Path: No Backslash, More Path: No Backslash */
+
+ _tcscpy(Path, testBasePathNoBackslash);
+
+ status = PathCchAppend(Path, PATHCCH_MAX_CCH, testMorePathNoBackslash);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAppend status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathOut) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathOut);
+ return -1;
+ }
+
+ /* According to msdn a NULL Path is an invalid argument */
+ status = PathCchAppend(NULL, PATHCCH_MAX_CCH, testMorePathNoBackslash);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(_T("PathCchAppend with NULL path unexpectedly returned status: 0x%08") _T(
+ PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* According to msdn a NULL pszMore is an invalid argument (although optional !?) */
+ _tcscpy(Path, testBasePathNoBackslash);
+ status = PathCchAppend(Path, PATHCCH_MAX_CCH, NULL);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(_T("PathCchAppend with NULL pszMore unexpectedly returned status: 0x%08") _T(
+ PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* According to msdn cchPath must be > 0 and <= PATHCCH_MAX_CCH */
+ _tcscpy(Path, testBasePathNoBackslash);
+ status = PathCchAppend(Path, 0, testMorePathNoBackslash);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(_T("PathCchAppend with cchPath value 0 unexpectedly returned status: 0x%08") _T(
+ PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+ _tcscpy(Path, testBasePathNoBackslash);
+ status = PathCchAppend(Path, PATHCCH_MAX_CCH + 1, testMorePathNoBackslash);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(_T("PathCchAppend with cchPath value > PATHCCH_MAX_CCH unexpectedly returned ")
+ _T("status: 0x%08") _T(PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* Resulting file must not exceed PATHCCH_MAX_CCH */
+
+ for (size_t i = 0; i < PATHCCH_MAX_CCH - 1; i++)
+ Path[i] = _T('X');
+
+ Path[PATHCCH_MAX_CCH - 1] = 0;
+
+ status = PathCchAppend(Path, PATHCCH_MAX_CCH, _T("\\This cannot be appended to Path"));
+ if (SUCCEEDED(status))
+ {
+ _tprintf(_T("PathCchAppend unexepectedly succeeded with status: 0x%08") _T(PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchAppendEx.c b/winpr/libwinpr/path/test/TestPathCchAppendEx.c
new file mode 100644
index 0000000..b6d83f5
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchAppendEx.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchAppendEx(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchCanonicalize.c b/winpr/libwinpr/path/test/TestPathCchCanonicalize.c
new file mode 100644
index 0000000..a7fa4ce
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchCanonicalize.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchCanonicalize(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchCanonicalizeEx.c b/winpr/libwinpr/path/test/TestPathCchCanonicalizeEx.c
new file mode 100644
index 0000000..2d670eb
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchCanonicalizeEx.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchCanonicalizeEx(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchCombine.c b/winpr/libwinpr/path/test/TestPathCchCombine.c
new file mode 100644
index 0000000..4b6f4e4
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchCombine.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchCombine(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchCombineEx.c b/winpr/libwinpr/path/test/TestPathCchCombineEx.c
new file mode 100644
index 0000000..89b794f
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchCombineEx.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchCombineEx(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchFindExtension.c b/winpr/libwinpr/path/test/TestPathCchFindExtension.c
new file mode 100644
index 0000000..f4b4151
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchFindExtension.c
@@ -0,0 +1,116 @@
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+static const char testPathExtension[] = "C:\\Windows\\System32\\cmd.exe";
+
+int TestPathCchFindExtension(int argc, char* argv[])
+{
+ PCSTR pszExt = NULL;
+ PCSTR pszTmp = NULL;
+ HRESULT hr = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* Test invalid args */
+
+ hr = PathCchFindExtensionA(NULL, sizeof(testPathExtension), &pszExt);
+ if (SUCCEEDED(hr))
+ {
+ printf(
+ "PathCchFindExtensionA unexpectedly succeeded with pszPath = NULL. result: 0x%08" PRIX32
+ "\n",
+ hr);
+ return -1;
+ }
+
+ hr = PathCchFindExtensionA(testPathExtension, 0, &pszExt);
+ if (SUCCEEDED(hr))
+ {
+ printf("PathCchFindExtensionA unexpectedly succeeded with cchPath = 0. result: 0x%08" PRIX32
+ "\n",
+ hr);
+ return -1;
+ }
+
+ hr = PathCchFindExtensionA(testPathExtension, sizeof(testPathExtension), NULL);
+ if (SUCCEEDED(hr))
+ {
+ printf(
+ "PathCchFindExtensionA unexpectedly succeeded with ppszExt = NULL. result: 0x%08" PRIX32
+ "\n",
+ hr);
+ return -1;
+ }
+
+ /* Test missing null-termination of pszPath */
+
+ hr = PathCchFindExtensionA("c:\\45.789", 9, &pszExt); /* nb: correct would be 10 */
+ if (SUCCEEDED(hr))
+ {
+ printf("PathCchFindExtensionA unexpectedly succeeded with unterminated pszPath. result: "
+ "0x%08" PRIX32 "\n",
+ hr);
+ return -1;
+ }
+
+ /* Test passing of an empty terminated string (must succeed) */
+
+ pszExt = NULL;
+ pszTmp = "";
+ hr = PathCchFindExtensionA(pszTmp, 1, &pszExt);
+ if (hr != S_OK)
+ {
+ printf("PathCchFindExtensionA failed with an empty terminated string. result: 0x%08" PRIX32
+ "\n",
+ hr);
+ return -1;
+ }
+ /* pszExt must point to the strings terminating 0 now */
+ if (pszExt != pszTmp)
+ {
+ printf("PathCchFindExtensionA failed with an empty terminated string: pszExt pointer "
+ "mismatch\n");
+ return -1;
+ }
+
+ /* Test a path without file extension (must succeed) */
+
+ pszExt = NULL;
+ pszTmp = "c:\\4.678\\";
+ hr = PathCchFindExtensionA(pszTmp, 10, &pszExt);
+ if (hr != S_OK)
+ {
+ printf("PathCchFindExtensionA failed with a directory path. result: 0x%08" PRIX32 "\n", hr);
+ return -1;
+ }
+ /* The extension must not have been found and pszExt must point to the
+ * strings terminating NULL now */
+ if (pszExt != &pszTmp[9])
+ {
+ printf("PathCchFindExtensionA failed with a directory path: pszExt pointer mismatch\n");
+ return -1;
+ }
+
+ /* Non-special tests */
+
+ pszExt = NULL;
+ if (PathCchFindExtensionA(testPathExtension, sizeof(testPathExtension), &pszExt) != S_OK)
+ {
+ printf("PathCchFindExtensionA failure: expected S_OK\n");
+ return -1;
+ }
+
+ if (!pszExt || strcmp(pszExt, ".exe"))
+ {
+ printf("PathCchFindExtensionA failure: unexpected extension\n");
+ return -1;
+ }
+
+ printf("Extension: %s\n", pszExt);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchIsRoot.c b/winpr/libwinpr/path/test/TestPathCchIsRoot.c
new file mode 100644
index 0000000..1ffda37
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchIsRoot.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchIsRoot(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchRemoveBackslash.c b/winpr/libwinpr/path/test/TestPathCchRemoveBackslash.c
new file mode 100644
index 0000000..f23e72b
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchRemoveBackslash.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchRemoveBackslash(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchRemoveBackslashEx.c b/winpr/libwinpr/path/test/TestPathCchRemoveBackslashEx.c
new file mode 100644
index 0000000..80eb1aa
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchRemoveBackslashEx.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchRemoveBackslashEx(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchRemoveExtension.c b/winpr/libwinpr/path/test/TestPathCchRemoveExtension.c
new file mode 100644
index 0000000..bd315cb
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchRemoveExtension.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchRemoveExtension(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchRemoveFileSpec.c b/winpr/libwinpr/path/test/TestPathCchRemoveFileSpec.c
new file mode 100644
index 0000000..686e367
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchRemoveFileSpec.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchRemoveFileSpec(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchRenameExtension.c b/winpr/libwinpr/path/test/TestPathCchRenameExtension.c
new file mode 100644
index 0000000..cf021b1
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchRenameExtension.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchRenameExtension(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchSkipRoot.c b/winpr/libwinpr/path/test/TestPathCchSkipRoot.c
new file mode 100644
index 0000000..dca5ad9
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchSkipRoot.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchSkipRoot(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchStripPrefix.c b/winpr/libwinpr/path/test/TestPathCchStripPrefix.c
new file mode 100644
index 0000000..aaec4bc
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchStripPrefix.c
@@ -0,0 +1,128 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+/**
+ * Naming Files, Paths, and Namespaces:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247/
+ */
+
+static const TCHAR testPathPrefixFileNamespace[] = _T("\\\\?\\C:\\Program Files\\");
+static const TCHAR testPathNoPrefixFileNamespace[] = _T("C:\\Program Files\\");
+static const TCHAR testPathPrefixFileNamespaceMinimum[] = _T("\\\\?\\C:");
+static const TCHAR testPathNoPrefixFileNamespaceMinimum[] = _T("C:");
+
+static const TCHAR testPathPrefixDeviceNamespace[] = _T("\\\\?\\GLOBALROOT");
+
+int TestPathCchStripPrefix(int argc, char* argv[])
+{
+ HRESULT status = 0;
+ TCHAR Path[PATHCCH_MAX_CCH];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /**
+ * PathCchStripPrefix returns S_OK if the prefix was removed, S_FALSE if
+ * the path did not have a prefix to remove, or an HRESULT failure code.
+ */
+
+ /* Path with prefix (File Namespace) */
+
+ _tcscpy(Path, testPathPrefixFileNamespace);
+
+ status = PathCchStripPrefix(Path, sizeof(testPathPrefixFileNamespace) / sizeof(TCHAR));
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchStripPrefix status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathNoPrefixFileNamespace) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path,
+ testPathNoPrefixFileNamespace);
+ return -1;
+ }
+
+ /* Path with prefix (Device Namespace) */
+
+ _tcscpy(Path, testPathPrefixDeviceNamespace);
+
+ status = PathCchStripPrefix(Path, sizeof(testPathPrefixDeviceNamespace) / sizeof(TCHAR));
+
+ if (status != S_FALSE)
+ {
+ _tprintf(_T("PathCchStripPrefix status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathPrefixDeviceNamespace) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path,
+ testPathPrefixDeviceNamespace);
+ return -1;
+ }
+
+ /* NULL Path */
+ status = PathCchStripPrefix(NULL, PATHCCH_MAX_CCH);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(
+ _T("PathCchStripPrefix with null path unexpectedly succeeded with status 0x%08") _T(
+ PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* Invalid cchPath values: 0, 1, 2, 3 and > PATHCCH_MAX_CCH */
+ for (int i = 0; i < 5; i++)
+ {
+ _tcscpy(Path, testPathPrefixFileNamespace);
+ if (i == 4)
+ i = PATHCCH_MAX_CCH + 1;
+ status = PathCchStripPrefix(Path, i);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(_T("PathCchStripPrefix with invalid cchPath value %d unexpectedly succeeded ")
+ _T("with status 0x%08") _T(PRIX32) _T("\n"),
+ i, status);
+ return -1;
+ }
+ }
+
+ /* Minimum Path that would get successfully stripped on windows */
+ _tcscpy(Path, testPathPrefixFileNamespaceMinimum);
+ size_t i = sizeof(testPathPrefixFileNamespaceMinimum) / sizeof(TCHAR);
+ i = i - 1; /* include testing of a non-null terminated string */
+ status = PathCchStripPrefix(Path, i);
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchStripPrefix with minimum valid strippable path length unexpectedly ")
+ _T("returned status 0x%08") _T(PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+ if (_tcscmp(Path, testPathNoPrefixFileNamespaceMinimum))
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path,
+ testPathNoPrefixFileNamespaceMinimum);
+ return -1;
+ }
+
+ /* Invalid drive letter symbol */
+ _tcscpy(Path, _T("\\\\?\\5:"));
+ status = PathCchStripPrefix(Path, 6);
+ if (status == S_OK)
+ {
+ _tprintf(
+ _T("PathCchStripPrefix with invalid drive letter symbol unexpectedly succeeded\n"));
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchStripToRoot.c b/winpr/libwinpr/path/test/TestPathCchStripToRoot.c
new file mode 100644
index 0000000..3b96e5c
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchStripToRoot.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchStripToRoot(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathIsUNCEx.c b/winpr/libwinpr/path/test/TestPathIsUNCEx.c
new file mode 100644
index 0000000..4fdf4f7
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathIsUNCEx.c
@@ -0,0 +1,52 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+static const TCHAR testServer[] = _T("server\\share\\path\\file");
+static const TCHAR testPathUNC[] = _T("\\\\server\\share\\path\\file");
+static const TCHAR testPathNotUNC[] = _T("C:\\share\\path\\file");
+
+int TestPathIsUNCEx(int argc, char* argv[])
+{
+ BOOL status = 0;
+ LPCTSTR Server = NULL;
+ TCHAR Path[PATHCCH_MAX_CCH];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* Path is UNC */
+
+ _tcscpy(Path, testPathUNC);
+
+ status = PathIsUNCEx(Path, &Server);
+
+ if (!status)
+ {
+ _tprintf(_T("PathIsUNCEx status: 0x%d\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Server, testServer) != 0)
+ {
+ _tprintf(_T("Server Name Mismatch: Actual: %s, Expected: %s\n"), Server, testServer);
+ return -1;
+ }
+
+ /* Path is not UNC */
+
+ _tcscpy(Path, testPathNotUNC);
+
+ status = PathIsUNCEx(Path, &Server);
+
+ if (status)
+ {
+ _tprintf(_T("PathIsUNCEx status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathMakePath.c b/winpr/libwinpr/path/test/TestPathMakePath.c
new file mode 100644
index 0000000..488cab3
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathMakePath.c
@@ -0,0 +1,83 @@
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+
+int TestPathMakePath(int argc, char* argv[])
+{
+ size_t baseLen = 0;
+ BOOL success = 0;
+ char tmp[64] = { 0 };
+ char* path = NULL;
+ char* cur = NULL;
+ char delim = PathGetSeparatorA(0);
+ char* base = GetKnownPath(KNOWN_PATH_TEMP);
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!base)
+ {
+ fprintf(stderr, "Failed to get temporary directory!\n");
+ return -1;
+ }
+
+ baseLen = strlen(base);
+ srand(time(NULL));
+
+ for (int x = 0; x < 5; x++)
+ {
+ sprintf_s(tmp, ARRAYSIZE(tmp), "%08X", rand());
+ path = GetCombinedPath(base, tmp);
+ free(base);
+
+ if (!path)
+ {
+ fprintf(stderr, "GetCombinedPath failed!\n");
+ return -1;
+ }
+
+ base = path;
+ }
+
+ printf("Creating path %s\n", path);
+ success = winpr_PathMakePath(path, NULL);
+
+ if (!success)
+ {
+ fprintf(stderr, "MakePath failed!\n");
+ free(path);
+ return -1;
+ }
+
+ success = winpr_PathFileExists(path);
+
+ if (!success)
+ {
+ fprintf(stderr, "MakePath lied about success!\n");
+ free(path);
+ return -1;
+ }
+
+ while (strlen(path) > baseLen)
+ {
+ if (!winpr_RemoveDirectory(path))
+ {
+ fprintf(stderr, "winpr_RemoveDirectory %s failed!\n", path);
+ free(path);
+ return -1;
+ }
+
+ cur = strrchr(path, delim);
+
+ if (cur)
+ *cur = '\0';
+ }
+
+ free(path);
+ printf("%s success!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathShell.c b/winpr/libwinpr/path/test/TestPathShell.c
new file mode 100644
index 0000000..1925fea
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathShell.c
@@ -0,0 +1,58 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathShell(int argc, char* argv[])
+{
+ const int paths[] = { KNOWN_PATH_HOME, KNOWN_PATH_TEMP,
+ KNOWN_PATH_XDG_DATA_HOME, KNOWN_PATH_XDG_CONFIG_HOME,
+ KNOWN_PATH_XDG_CACHE_HOME, KNOWN_PATH_XDG_RUNTIME_DIR,
+ KNOWN_PATH_XDG_CONFIG_HOME };
+ const char* names[] = { "KNOWN_PATH_HOME", "KNOWN_PATH_TEMP",
+ "KNOWN_PATH_XDG_DATA_HOME", "KNOWN_PATH_XDG_CONFIG_HOME",
+ "KNOWN_PATH_XDG_CACHE_HOME", "KNOWN_PATH_XDG_RUNTIME_DIR",
+ "KNOWN_PATH_XDG_CONFIG_HOME" };
+ int rc = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ for (size_t x = 0; x < sizeof(paths) / sizeof(paths[0]); x++)
+ {
+ const int id = paths[x];
+ const char* name = names[x];
+ {
+ char* path = GetKnownPath(id);
+
+ if (!path)
+ {
+ fprintf(stderr, "GetKnownPath(%d) failed\n", id);
+ rc = -1;
+ }
+ else
+ {
+ printf("%s Path: %s\n", name, path);
+ }
+ free(path);
+ }
+ {
+ char* path = GetKnownSubPath(id, "freerdp");
+
+ if (!path)
+ {
+ fprintf(stderr, "GetKnownSubPath(%d) failed\n", id);
+ rc = -1;
+ }
+ else
+ {
+ printf("%s SubPath: %s\n", name, path);
+ }
+ free(path);
+ }
+ }
+
+ return rc;
+}