From 6d146da38d626a6c26c9aa94428fe70818968276 Mon Sep 17 00:00:00 2001 From: Enzo Evers Date: Thu, 5 Jun 2025 10:49:08 +0200 Subject: [PATCH] Normilize path separators --- CoDeLib/FileUtils/src/FileUtils.c | 43 +++++- CoDeLib/Test/src/TestFileUtils.c | 143 +++++++++++------- CoDeLib/Test/src/main.c | 18 ++- CoDeLib/include/CoDeLib/FileUtils/FileUtils.h | 19 +++ 4 files changed, 160 insertions(+), 63 deletions(-) diff --git a/CoDeLib/FileUtils/src/FileUtils.c b/CoDeLib/FileUtils/src/FileUtils.c index 2d0b4b23..75b6c843 100644 --- a/CoDeLib/FileUtils/src/FileUtils.c +++ b/CoDeLib/FileUtils/src/FileUtils.c @@ -54,6 +54,34 @@ int _HandleFtwCallback_Remove(const char *fpath, const struct stat *sb, // Public function implementations //========================= +bool IsPathNormalized(const char *const pPath) { + if (pPath == NULL) { + return false; + } + + for (size_t i = 0; i < strlen(pPath); ++i) { + if (pPath[i] == '\\') { + return false; + } + } + + return true; +} + +char *NormailizePathSeparatorsInPlace(char *pPath) { + if (pPath == NULL) { + return NULL; + } + + for (size_t i = 0; i < strlen(pPath); ++i) { + if (pPath[i] == '\\') { + pPath[i] = '/'; + } + } + + return pPath; +} + // Based on // https://nachtimwald.com/2019/07/10/recursive-create-directory-in-c-revisited/ bool RecursiveMkdir(const char *const pDirname) { @@ -114,9 +142,11 @@ bool RecursiveMkdir(const char *const pDirname) { // Adds null terminator after the seperator pPathToCreate[dirnamePartLength] = '\0'; - success = _CreateDir(pPathToCreate); - if (!success) { - break; + if (!PathExists(pPathToCreate)) { + success = _CreateDir(pPathToCreate); + if (!success) { + break; + } } pTargetPath++; @@ -171,7 +201,8 @@ bool IsAbsolutePath(const char *pPath) { bool GetAbsolutePath(const char *pPath, char *const pAbsolutePath, const size_t absolutePathSize) { - if (pPath == NULL || pAbsolutePath == NULL || absolutePathSize == 0) { + if (pPath == NULL || pAbsolutePath == NULL || absolutePathSize == 0 || + !IsPathNormalized(pPath)) { return false; } @@ -201,6 +232,8 @@ bool GetAbsolutePath(const char *pPath, char *const pAbsolutePath, memcpy(&pAbsolutePath[0], pPath, sizeof(char) * (pathLenght + 1)); } + NormailizePathSeparatorsInPlace(pAbsolutePath); + return true; } @@ -232,6 +265,8 @@ char *GetCurrentWorkingDirectory(char *pFullPath, size_t fullPathSize) { pFullPath[currentPathLength + 1] = '\0'; } + NormailizePathSeparatorsInPlace(pFullPath); + return pFullPath; } diff --git a/CoDeLib/Test/src/TestFileUtils.c b/CoDeLib/Test/src/TestFileUtils.c index 11ce3ba7..467c7ea3 100644 --- a/CoDeLib/Test/src/TestFileUtils.c +++ b/CoDeLib/Test/src/TestFileUtils.c @@ -36,6 +36,62 @@ TEST_TEAR_DOWN(TestFileUtils) { } } +//============================== +// IsPathNormalized(...) +//============================== + +TEST(TestFileUtils, test_IsPathNormalized_ReturnsFalseIfPathIsNull) { + TEST_ASSERT_FALSE(IsPathNormalized(NULL)); +} + +TEST(TestFileUtils, test_IsPathNormalized_ReturnsTrueIfPathIsEmpty) { + TEST_ASSERT_TRUE(IsPathNormalized("")); +} + +TEST(TestFileUtils, test_IsPathNormalized_ReturnsFalseIfPathIsNotNormalized) { + TEST_ASSERT_FALSE(IsPathNormalized("a\\b/c\\d")); +} + +TEST(TestFileUtils, test_IsPathNormalized_ReturnsTrueIfPathIsNormalized) { + TEST_ASSERT_TRUE(IsPathNormalized("a/b/c/d")); +} + +//============================== +// NormailizePathSeparatorsInPlace(...) +//============================== + +TEST(TestFileUtils, + test_NormailizePathSeparatorsInPlace_ReturnsFalseIfPathIsNull) { + TEST_ASSERT_NULL(NormailizePathSeparatorsInPlace(NULL)); +} + +TEST(TestFileUtils, + test_NormailizePathSeparatorsInPlace_NormalizesPathSeparators) { + RAII_STRING path = RaiiStringCreateFromCString("a\\b/c\\d"); + + char *pNormalizedPath = NormailizePathSeparatorsInPlace(path.pString); + TEST_ASSERT_EQUAL(pNormalizedPath, path.pString); + TEST_ASSERT_EQUAL_STRING("a/b/c/d", path.pString); +} + +TEST(TestFileUtils, + test_NormailizePathSeparatorsInPlace_DoesNotChangeAlreadyNormalized) { + RAII_STRING path = RaiiStringCreateFromCString("a/b/c/d"); + + char *pNormalizedPath = NormailizePathSeparatorsInPlace(path.pString); + TEST_ASSERT_EQUAL(pNormalizedPath, path.pString); + TEST_ASSERT_EQUAL_STRING("a/b/c/d", path.pString); +} + +TEST(TestFileUtils, + test_NormailizePathSeparatorsInPlace_NormalizesWithWindowsStylePrefix) { + RAII_STRING path = RaiiStringCreateFromCString("C:\\a\\b\\"); + + char *pNormalizedPath = NormailizePathSeparatorsInPlace(path.pString); + TEST_ASSERT_EQUAL(pNormalizedPath, path.pString); + TEST_ASSERT_EQUAL_STRING("C:/a/b/", path.pString); +} + //============================== // IsAbsolutePath(...) //============================== @@ -213,39 +269,21 @@ TEST(TestFileUtils, TEST_ASSERT_TRUE(success); } -TEST( - TestFileUtils, - test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_TrailingSlash_WindowsStyle) { - char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; - char *pPath = "C:\\a\\b\\"; - - bool success = - GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR); - - TEST_ASSERT_TRUE(success); - TEST_ASSERT_NOT_EQUAL(pPath, &localBuffer[0]); - TEST_ASSERT_EQUAL_STRING(pPath, localBuffer); -} - -TEST( - TestFileUtils, - test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_NoTrailingSlash_WindowsStyle) { +TEST(TestFileUtils, test_GetAbsolutePath_ReturnsFalseIfIfIsNotNormalized) { char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; - char *pPath = "C:\\a\\b"; + char *pPath = "C:\\a/b\\"; bool success = GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR); - TEST_ASSERT_TRUE(success); - TEST_ASSERT_NOT_EQUAL(pPath, &localBuffer[0]); - TEST_ASSERT_EQUAL_STRING(pPath, localBuffer); + TEST_ASSERT_FALSE(success); } TEST( TestFileUtils, - test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_FileExtention_WindowsStyle) { + test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_TrailingSlash_PosixStyleWindowsPrefix) { char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; - char *pPath = "C:\\a\\b.txt"; + char *pPath = "C:/a/b/"; bool success = GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR); @@ -297,28 +335,6 @@ TEST( TEST_ASSERT_EQUAL_STRING(pPath, localBuffer); } -TEST( - TestFileUtils, - test_GetAbsolutePath_ReturnsTrueIfPathIsRelativeAndCopiesItToBuffer_TrailingSlash_WindowsStyle) { - char expectedAbsolutePathBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; - GetCurrentWorkingDirectory(expectedAbsolutePathBuffer, - MAX_PATH_LENGTH_WTH_TERMINATOR); - RAII_STRING expectedAbsolutePath = - RaiiStringCreateFromCString(expectedAbsolutePathBuffer); - - char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; - char *pPath = "b\\"; - - RaiiStringAppend_cString(&expectedAbsolutePath, pPath); - - bool success = - GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR); - - TEST_ASSERT_TRUE(success); - TEST_ASSERT_GREATER_THAN(strlen(pPath), strlen(&localBuffer[0])); - TEST_ASSERT_EQUAL_STRING(expectedAbsolutePath.pString, localBuffer); -} - TEST( TestFileUtils, test_GetAbsolutePath_ReturnsTrueIfPathIsRelativeAndCopiesItToBuffer_TrailingSlash_PosixStyle) { @@ -1204,6 +1220,30 @@ TEST(TestFileUtils, //============================== TEST_GROUP_RUNNER(TestFileUtils) { + // IsPathNormalized(...) + RUN_TEST_CASE(TestFileUtils, + test_IsPathNormalized_ReturnsFalseIfPathIsNull); + RUN_TEST_CASE(TestFileUtils, + test_IsPathNormalized_ReturnsTrueIfPathIsEmpty); + RUN_TEST_CASE(TestFileUtils, + test_IsPathNormalized_ReturnsFalseIfPathIsNotNormalized); + RUN_TEST_CASE(TestFileUtils, + test_IsPathNormalized_ReturnsTrueIfPathIsNormalized); + + // NormalizePathSeparator(...) + RUN_TEST_CASE( + TestFileUtils, + test_NormailizePathSeparatorsInPlace_ReturnsFalseIfPathIsNull); + RUN_TEST_CASE( + TestFileUtils, + test_NormailizePathSeparatorsInPlace_NormalizesPathSeparators); + RUN_TEST_CASE( + TestFileUtils, + test_NormailizePathSeparatorsInPlace_DoesNotChangeAlreadyNormalized); + RUN_TEST_CASE( + TestFileUtils, + test_NormailizePathSeparatorsInPlace_NormalizesWithWindowsStylePrefix); + // IsAbsolutePath(...) RUN_TEST_CASE(TestFileUtils, test_IsAbsolutePath_ReturnsFalseIfPathIsNull); RUN_TEST_CASE(TestFileUtils, test_IsAbsolutePath_ReturnsFalseIfPathIsEmpty); @@ -1257,15 +1297,11 @@ TEST_GROUP_RUNNER(TestFileUtils) { RUN_TEST_CASE( TestFileUtils, test_GetAbsolutePath_AllowsBuffersToBeBiggerThanMaxPathLength); + RUN_TEST_CASE(TestFileUtils, + test_GetAbsolutePath_ReturnsFalseIfIfIsNotNormalized); RUN_TEST_CASE( TestFileUtils, - test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_TrailingSlash_WindowsStyle); - RUN_TEST_CASE( - TestFileUtils, - test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_NoTrailingSlash_WindowsStyle); - RUN_TEST_CASE( - TestFileUtils, - test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_FileExtention_WindowsStyle); + test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_TrailingSlash_PosixStyleWindowsPrefix); RUN_TEST_CASE( TestFileUtils, test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_TrailingSlash_PosixStyle); @@ -1275,9 +1311,6 @@ TEST_GROUP_RUNNER(TestFileUtils) { RUN_TEST_CASE( TestFileUtils, test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_FileExtentions_PosixStyle); - RUN_TEST_CASE( - TestFileUtils, - test_GetAbsolutePath_ReturnsTrueIfPathIsRelativeAndCopiesItToBuffer_TrailingSlash_WindowsStyle); RUN_TEST_CASE( TestFileUtils, test_GetAbsolutePath_ReturnsTrueIfPathIsRelativeAndCopiesItToBuffer_TrailingSlash_PosixStyle); diff --git a/CoDeLib/Test/src/main.c b/CoDeLib/Test/src/main.c index f6efc7b5..e400ac43 100644 --- a/CoDeLib/Test/src/main.c +++ b/CoDeLib/Test/src/main.c @@ -22,10 +22,10 @@ static void RunAllTests(void) { } int main(int argc, const char **argv) { - RAII_STRING fullPathToBenchmarkTestFiles; + RAII_STRING fullPathToBenchmarkTestFiles = {NULL, 0}; // Should end with '/' - RAII_STRING currentWorkingDirectory; + RAII_STRING currentWorkingDirectory = {NULL, 0}; bool runLongTests = false; // TODO: use getopt(...) @@ -33,8 +33,18 @@ int main(int argc, const char **argv) { printf("Not enough arguments provided\n"); return 1; } else { - fullPathToBenchmarkTestFiles = RaiiStringCreateFromCString(argv[1]); - currentWorkingDirectory = RaiiStringCreateFromCString(argv[2]); + RAII_STRING unNormalizedFullPathToBenchmarkTestFiles = + RaiiStringCreateFromCString(argv[1]); + fullPathToBenchmarkTestFiles = + RaiiStringCreateFromCString(NormailizePathSeparatorsInPlace( + unNormalizedFullPathToBenchmarkTestFiles.pString)); + + RAII_STRING unNormalizedCurrentWorkingDirectory = + RaiiStringCreateFromCString(argv[2]); + currentWorkingDirectory = + RaiiStringCreateFromCString(NormailizePathSeparatorsInPlace( + unNormalizedCurrentWorkingDirectory.pString)); + runLongTests = strcmp(argv[3], "true") == 0; } diff --git a/CoDeLib/include/CoDeLib/FileUtils/FileUtils.h b/CoDeLib/include/CoDeLib/FileUtils/FileUtils.h index 398f53c6..a66c3015 100644 --- a/CoDeLib/include/CoDeLib/FileUtils/FileUtils.h +++ b/CoDeLib/include/CoDeLib/FileUtils/FileUtils.h @@ -8,6 +8,25 @@ #define MAX_PATH_LENGTH 256 #define MAX_PATH_LENGTH_WTH_TERMINATOR (MAX_PATH_LENGTH + 1) +#define PATH_SEPARATOR '/' + +/*! +@brief Checks if a path is normalized. A normalized path has no escaped +backward slashes ('\\') and uses forward slashes ('/') as path separators. +@param pPath The path to check. +@return true if the path is normalized, false otherwise. +*/ +bool IsPathNormalized(const char *const pPath); + +/*! +@brief Normalizes the path separators in a path. This function replaces all +escaped backward slashes ('\\') with forward slashes ('/'). +@param pPath The path to normalize. The path will be modified in place. +@return pPath if the path was successfully normalized and placed in pDestPath, +NULL otherwise +*/ +char *NormailizePathSeparatorsInPlace(char *pPath); + /*! @brief Recursively creates a directory. If the directory already exists, nothing happens.