path: Also accept / as directory separator on Windows
authorTobias Brunner <tobias@strongswan.org>
Mon, 1 Feb 2021 13:35:09 +0000 (14:35 +0100)
committerTobias Brunner <tobias@strongswan.org>
Wed, 3 Feb 2021 16:27:57 +0000 (17:27 +0100)
This adds helper functions to determine the first or last directory separator
in a string and to check if a given character is a separator.

Paths starting with a separator are now also considered absolute on
Windows as these are rooted at the current drive.

Note that it's fine to use DIRECTORY_SEPARATOR when combining strings as
Windows API calls accept both forward and backward slashes as separators.

Co-authored-by: MichaƂ Skalski <mskalski@enigma.com.pl>
References #3684.

src/libimcv/plugins/imv_attestation/attest_db.c
src/libstrongswan/collections/enumerator.c
src/libstrongswan/tests/suites/test_utils.c
src/libstrongswan/utils/utils/path.c
src/libstrongswan/utils/utils/path.h

index bc435df..1157a17 100644 (file)
@@ -337,7 +337,7 @@ METHOD(attest_db_t, set_directory, bool,
 
        /* remove trailing '/' or '\' character if not root directory */
        len = strlen(dir);
-       if (len > 1 && dir[len-1] == DIRECTORY_SEPARATOR[0])
+       if (len > 1 && path_is_separator(dir[len-1]))
        {
                dir[len-1] = '\0';
        }
index a663d52..1dd1118 100644 (file)
@@ -173,9 +173,9 @@ enumerator_t* enumerator_create_directory(const char *path)
                return NULL;
        }
        /* append a '/' if not already done */
-       if (this->full[len-1] != '/')
+       if (!path_is_separator(this->full[len-1]))
        {
-               this->full[len++] = '/';
+               this->full[len++] = DIRECTORY_SEPARATOR[0];
                this->full[len] = '\0';
        }
        this->full_end = &this->full[len];
index fd7cb64..ef26193 100644 (file)
@@ -767,6 +767,97 @@ START_TEST(test_strreplace)
 END_TEST
 
 /*******************************************************************************
+ * path_first/last_separator
+ */
+
+static struct {
+       char *path;
+       int len;
+       int first;
+       int last;
+} separator_data[] = {
+       {NULL, -1, -1, -1},
+       {"",   -1, -1, -1},
+       {".",  -1, -1, -1},
+       {"..", -1, -1, -1},
+#ifdef WIN32
+       {"C:\\",         -1, 2, 2},
+       {"C:/",          -1, 2, 2},
+       {"X:\\\\",       -1, 2, 3},
+       {"d:\\f",        -1, 2, 2},
+       {"d:\\f",         2, -1, -1},
+       {"C:\\foo\\",    -1, 2, 6},
+       {"foo\\bar",     -1, 3, 3},
+       {"foo\\\\bar",   -1, 3, 4},
+       {"\\foo\\bar",   -1, 0, 4},
+       {"\\\\foo\\bar", -1, 0, 5},
+       {"\\\\foo\\bar",  4, 0, 1},
+       {"foo\\bar/baz", -1, 3, 7},
+#endif /* WIN32 */
+       {"/",         -1, 0, 0},
+       {"//",        -1, 0, 1},
+       {"foo",       -1, -1, -1},
+       {"f/",        -1, 1, 1},
+       {"foo/",      -1, 3, 3},
+       {"foo/",       2, -1, -1},
+       {"foo//",     -1, 3, 4},
+       {"/foo",      -1, 0, 0},
+       {"/foo/",     -1, 0, 4},
+       {"/foo/",      3, 0, 0},
+       {"//foo/",    -1, 0, 5},
+       {"foo/bar",   -1, 3, 3},
+       {"foo/bar",    1, -1, -1},
+       {"foo/bar",    2, -1, -1},
+       {"foo/bar",    3, -1, -1},
+       {"foo/bar",    4, 3, 3},
+       {"foo/bar",    5, 3, 3},
+       {"foo//bar",  -1, 3, 4},
+       {"/foo/bar",  -1, 0, 4},
+       {"/foo/bar/", -1, 0, 8},
+       {"/foo/bar/",  0, -1, -1},
+       {"/foo/bar/",  1, 0, 0},
+       {"/foo/bar/",  2, 0, 0},
+       {"/foo/bar/",  3, 0, 0},
+       {"/foo/bar/",  4, 0, 0},
+       {"/foo/bar/",  5, 0, 4},
+       {"/foo/bar/",  7, 0, 4},
+       {"/foo/bar/",  8, 0, 4},
+       {"/foo/bar/",  9, 0, 8},
+};
+
+START_TEST(test_path_first_separator)
+{
+       char *pos;
+
+       pos = path_first_separator(separator_data[_i].path, separator_data[_i].len);
+       if (separator_data[_i].first >= 0)
+       {
+               ck_assert_int_eq(pos-separator_data[_i].path, separator_data[_i].first);
+       }
+       else
+       {
+               ck_assert(!pos);
+       }
+}
+END_TEST
+
+START_TEST(test_path_last_separator)
+{
+       char *pos;
+
+       pos = path_last_separator(separator_data[_i].path, separator_data[_i].len);
+       if (separator_data[_i].last >= 0)
+       {
+               ck_assert_int_eq(pos-separator_data[_i].path, separator_data[_i].last);
+       }
+       else
+       {
+               ck_assert(!pos);
+       }
+}
+END_TEST
+
+/*******************************************************************************
  * path_dirname/basename/absolute
  */
 
@@ -782,6 +873,7 @@ static struct {
        {"..", ".", "..", FALSE},
 #ifdef WIN32
        {"C:\\", "C:", "C:", TRUE},
+       {"C:/", "C:", "C:", TRUE},
        {"X:\\\\", "X:", "X:", TRUE},
        {"foo", ".", "foo", FALSE},
        {"f\\", ".", "f", FALSE},
@@ -789,16 +881,20 @@ static struct {
        {"foo\\\\", ".", "foo", FALSE},
        {"d:\\f", "d:", "f", TRUE},
        {"C:\\f\\", "C:", "f", TRUE},
+       {"C:\\f\\", "C:", "f", TRUE},
        {"C:\\foo", "C:", "foo", TRUE},
        {"C:\\foo\\", "C:", "foo", TRUE},
+       {"C:\\foo/", "C:", "foo", TRUE},
        {"foo\\bar", "foo", "bar", FALSE},
        {"foo\\\\bar", "foo", "bar", FALSE},
        {"C:\\foo\\bar", "C:\\foo", "bar", TRUE},
        {"C:\\foo\\bar\\", "C:\\foo", "bar", TRUE},
        {"C:\\foo\\bar\\baz", "C:\\foo\\bar", "baz", TRUE},
-       {"\\foo\\bar", "\\foo", "bar", FALSE},
+       {"C:\\foo/bar\\baz", "C:\\foo/bar", "baz", TRUE},
+       {"C:/foo/bar/baz", "C:/foo/bar", "baz", TRUE},
+       {"\\foo\\bar", "\\foo", "bar", TRUE},
        {"\\\\foo\\bar", "\\\\foo", "bar", TRUE},
-#else /* !WIN32 */
+#endif /* WIN32 */
        {"/", "/", "/", TRUE},
        {"//", "/", "/", TRUE},
        {"foo", ".", "foo", FALSE},
@@ -815,7 +911,6 @@ static struct {
        {"/foo/bar", "/foo", "bar", TRUE},
        {"/foo/bar/", "/foo", "bar", TRUE},
        {"/foo/bar/baz", "/foo/bar", "baz", TRUE},
-#endif
 };
 
 START_TEST(test_path_dirname)
@@ -1207,6 +1302,11 @@ Suite *utils_suite_create()
        tcase_add_loop_test(tc, test_strreplace, 0, countof(strreplace_data));
        suite_add_tcase(s, tc);
 
+       tc = tcase_create("path_first/last_separator");
+       tcase_add_loop_test(tc, test_path_first_separator, 0, countof(separator_data));
+       tcase_add_loop_test(tc, test_path_last_separator, 0, countof(separator_data));
+       suite_add_tcase(s, tc);
+
        tc = tcase_create("path_dirname");
        tcase_add_loop_test(tc, test_path_dirname, 0, countof(path_data));
        suite_add_tcase(s, tc);
index d964c70..fc7cb37 100644 (file)
 #include <unistd.h>
 #include <sys/stat.h>
 
-/**
- * Described in header.
+/*
+ * Described in header
  */
-char* path_dirname(const char *path)
+char *path_first_separator(const char *path, int len)
+{
+       if (!path)
+       {
+               return NULL;
+       }
+       if (len < 0)
+       {
+               len = strlen(path);
+       }
+       for (; len; path++, len--)
+       {
+               if (path_is_separator(*path))
+               {
+                       return (char*)path;
+               }
+       }
+       return NULL;
+}
+
+/*
+ * Described in header
+ */
+char *path_last_separator(const char *path, int len)
+{
+       if (!path)
+       {
+               return NULL;
+       }
+       if (len < 0)
+       {
+               len = strlen(path);
+       }
+       while (len)
+       {
+               if (path_is_separator(path[--len]))
+               {
+                       return (char*)&path[len];
+               }
+       }
+       return NULL;
+}
+
+/*
+ * Described in header
+ */
+char *path_dirname(const char *path)
 {
        char *pos;
 
-       pos = path ? strrchr(path, DIRECTORY_SEPARATOR[0]) : NULL;
+       pos = path_last_separator(path, -1);
 
        if (pos && !pos[1])
-       {       /* if path ends with slashes we have to look beyond them */
-               while (pos > path && *pos == DIRECTORY_SEPARATOR[0])
-               {       /* skip trailing slashes */
+       {       /* if path ends with separators, we have to look beyond them */
+               while (pos > path && path_is_separator(*pos))
+               {       /* skip trailing separators */
                        pos--;
                }
-               pos = memrchr(path, DIRECTORY_SEPARATOR[0], pos - path + 1);
+               pos = path_last_separator(path, pos - path + 1);
        }
        if (!pos)
        {
@@ -54,17 +100,17 @@ char* path_dirname(const char *path)
 #endif
                return strdup(".");
        }
-       while (pos > path && *pos == DIRECTORY_SEPARATOR[0])
-       {       /* skip superfluous slashes */
+       while (pos > path && path_is_separator(*pos))
+       {       /* skip superfluous separators */
                pos--;
        }
        return strndup(path, pos - path + 1);
 }
 
-/**
- * Described in header.
+/*
+ * Described in header
  */
-charpath_basename(const char *path)
+char *path_basename(const char *path)
 {
        char *pos, *trail = NULL;
 
@@ -72,26 +118,26 @@ char* path_basename(const char *path)
        {
                return strdup(".");
        }
-       pos = strrchr(path, DIRECTORY_SEPARATOR[0]);
+       pos = path_last_separator(path, -1);
        if (pos && !pos[1])
-       {       /* if path ends with slashes we have to look beyond them */
-               while (pos > path && *pos == DIRECTORY_SEPARATOR[0])
-               {       /* skip trailing slashes */
+       {       /* if path ends with separators, we have to look beyond them */
+               while (pos > path && path_is_separator(*pos))
+               {       /* skip trailing separators */
                        pos--;
                }
-               if (pos == path && *pos == DIRECTORY_SEPARATOR[0])
-               {       /* contains only slashes */
-                       return strdup(DIRECTORY_SEPARATOR);
+               if (pos == path && path_is_separator(*pos))
+               {       /* contains only separators */
+                       return strndup(pos, 1);
                }
                trail = pos + 1;
-               pos = memrchr(path, DIRECTORY_SEPARATOR[0], trail - path);
+               pos = path_last_separator(path, trail - path);
        }
        pos = pos ? pos + 1 : (char*)path;
        return trail ? strndup(pos, trail - pos) : strdup(pos);
 }
 
-/**
- * Described in header.
+/*
+ * Described in header
  */
 bool path_absolute(const char *path)
 {
@@ -108,22 +154,21 @@ bool path_absolute(const char *path)
        {       /* drive letter */
                return TRUE;
        }
-#else /* !WIN32 */
-       if (path[0] == DIRECTORY_SEPARATOR[0])
+#endif /* WIN32 */
+       if (path_is_separator(path[0]))
        {
                return TRUE;
        }
-#endif
        return FALSE;
 }
 
-/**
- * Described in header.
+/*
+ * Described in header
  */
 bool mkdir_p(const char *path, mode_t mode)
 {
        int len;
-       char *pos, full[PATH_MAX];
+       char *pos, sep, full[PATH_MAX];
        pos = full;
        if (!path || *path == '\0')
        {
@@ -135,19 +180,20 @@ bool mkdir_p(const char *path, mode_t mode)
                DBG1(DBG_LIB, "path string %s too long", path);
                return FALSE;
        }
-       /* ensure that the path ends with a '/' */
-       if (full[len-1] != '/')
+       /* ensure that the path ends with a separator */
+       if (!path_is_separator(full[len-1]))
        {
-               full[len++] = '/';
+               full[len++] = DIRECTORY_SEPARATOR[0];
                full[len] = '\0';
        }
-       /* skip '/' at the beginning */
-       while (*pos == '/')
+       /* skip separators at the beginning */
+       while (path_is_separator(*pos))
        {
                pos++;
        }
-       while ((pos = strchr(pos, '/')))
+       while ((pos = path_first_separator(pos, -1)))
        {
+               sep = *pos;
                *pos = '\0';
                if (access(full, F_OK) < 0)
                {
@@ -161,7 +207,7 @@ bool mkdir_p(const char *path, mode_t mode)
                                return FALSE;
                        }
                }
-               *pos = '/';
+               *pos = sep;
                pos++;
        }
        return TRUE;
index b72bdaf..de00535 100644 (file)
  */
 #ifdef WIN32
 # define DIRECTORY_SEPARATOR "\\"
+/* the Windows API also accepts / as separator */
+# define DIRECTORY_SEPARATOR_ALT "/"
 #else
 # define DIRECTORY_SEPARATOR "/"
 #endif
 
 /**
+ * Check if the given character is a directory separator.
+ *
+ * @param c                    character
+ * @return                     TRUE if the character is a directory separator
+ */
+static inline bool path_is_separator(char c)
+{
+#ifdef WIN32
+       return c == DIRECTORY_SEPARATOR[0] || c == DIRECTORY_SEPARATOR_ALT[0];
+#else
+       return c == DIRECTORY_SEPARATOR[0];
+#endif
+}
+
+/**
+ * Returns a pointer to the first occurrence of a directory separator in the
+ * given string (optionally limited to a given length).
+ *
+ * @param path         path to search
+ * @param len          optional length to search, -1 for the full string
+ * @return                     pointer to separator, or NULL if not found
+ */
+char *path_first_separator(const char *path, int len);
+
+/**
+ * Returns a pointer to the last occurrence of a directory separator in the
+ * given string (optionally limited to a given length).
+ *
+ * @param path         path to search
+ * @param len          optional length to search, -1 for the full string
+ * @return                     pointer to separator, or NULL if not found
+ */
+char *path_last_separator(const char *path, int len);
+
+/**
  * Like dirname(3) returns the directory part of the given null-terminated
  * pathname, up to but not including the final '/' (or '.' if no '/' is found).
  * Trailing '/' are not counted as part of the pathname.