common: added mrp_normalize_path to file-utils.
authorKrisztian Litkey <kli@iki.fi>
Sat, 1 Nov 2014 08:42:54 +0000 (10:42 +0200)
committerKrisztian Litkey <kli@iki.fi>
Sat, 1 Nov 2014 09:06:26 +0000 (11:06 +0200)
src/common/file-utils.c
src/common/file-utils.h
src/common/tests/Makefile.am
src/common/tests/path-test.c [new file with mode: 0644]

index 67456fb..ec52bec 100644 (file)
@@ -31,6 +31,7 @@
 #include <unistd.h>
 #include <errno.h>
 #include <string.h>
+#include <stdbool.h>
 #include <dirent.h>
 #include <regex.h>
 #include <sys/types.h>
@@ -272,3 +273,112 @@ int mrp_mkdir(const char *path, mode_t mode)
 
     return -1;
 }
+
+
+char *mrp_normalize_path(char *buf, size_t size, const char *path)
+{
+    const char *p;
+    char       *q;
+    int         n, back[PATH_MAX / 2];
+
+    if (path == NULL)
+        return NULL;
+
+    if (*path == '\0') {
+        if (size > 0) {
+            *buf = '\0';
+            return buf;
+        }
+        else {
+        overflow:
+            errno = ENAMETOOLONG;
+            return NULL;
+        }
+    }
+
+    p   = path;
+    q   = buf;
+    n   = 0;
+
+    while (*p) {
+        if (q - buf + 1 >= (ptrdiff_t)size)
+            goto overflow;
+
+        switch (*p) {
+        case '/':
+            back[n++] = q - buf;
+            *q++ = *p++;
+
+        skip_slashes:
+            while (*p == '/')
+                p++;
+
+            /*
+             * '.'
+             *
+             * We skip './' including all trailing slashes. Note that
+             * the code is arranged so that whenever we skip trailing
+             * slashes, we automatically check and skip also trailing
+             * './'s too...
+             */
+
+            if (p[0] == '.' && (p[1] == '/' || p[1] == '\0')) {
+                p++;
+                goto skip_slashes;
+            }
+
+            /*
+             * '..'
+             *
+             * We throw away the last incorrectly saved backtracking
+             * point (we saved it for this '../'). Then if we can still
+             * backtrack, we do so. Otherwise (we're at the beginning
+             * already), if the path is absolute, we just ignore the
+             * current '../' (can't go above '/'), otherwise we keep it
+             * for relative pathes.
+             */
+
+            if (p[0] == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0')) {
+                n--;                                /* throw away */
+                if (n > 0) {                        /* can still backtrack */
+                    if (back[n - 1] >= 0)           /* previous not a '..' */
+                        q = buf + back[n - 1] + 1;
+                }
+                else {
+                    if (q > buf && buf[0] == '/')   /* for absolute pathes */
+                        q = buf + 1;                /*     reset to start */
+                    else {                          /* for relative pathes */
+                        if (q - buf + 4 >= (ptrdiff_t)size)
+                            goto overflow;
+
+                        q[0] = '.';                 /*     append this '..' */
+                        q[1] = '.';
+                        q[2] = '/';
+                        q += 3;
+                        back[n] = -1;               /*     block backtracking */
+                    }
+                }
+
+                p += 2;
+                goto skip_slashes;
+            }
+            break;
+
+        default:
+            *q++ = *p++;
+            break;
+        }
+    }
+
+    /*
+     * finally for other than '/' align trailing slashes
+     */
+
+    if (p > path + 1 && p[-1] != '/')
+        if (q > buf + 1 && q[-1] == '/')
+            q--;
+
+    *q = '\0';
+
+    return buf;
+}
index d6e9afc..dd7266c 100644 (file)
@@ -72,4 +72,8 @@ int mrp_find_file(const char *file, const char **dirs, int mode, char *buf,
 /** Create a directory, creating leading path as necessary. */
 int mrp_mkdir(const char *path, mode_t mode);
 
+
+/** Parse a path into a normalized form, removing ../'s and ./'s. */
+char *mrp_normalize_path(char *buf, size_t size, const char *path);
+
 #endif /* __MURPHY_FILEUTILS_H__ */
index 9fd30fb..930442f 100644 (file)
@@ -2,7 +2,7 @@ AM_CFLAGS = $(WARNING_CFLAGS) -I$(top_builddir)
 
 noinst_PROGRAMS  = mm-test hash-test hash12-test msg-test transport-test \
                  internal-transport-test process-watch-test native-test \
-                mkdir-test
+                mkdir-test path-test
 
 if LIBDBUS_ENABLED
 noinst_PROGRAMS += mainloop-test dbus-test
@@ -133,3 +133,8 @@ fragbuf_test_LDADD   = ../../libmurphy-common.la
 mkdir_test_SOURCES = mkdir-test.c
 mkdir_test_CFLAGS  = $(AM_CFLAGS) -I.
 mkdir_test_LDADD   = ../../libmurphy-common.la
+
+# path-test
+path_test_SOURCES = path-test.c
+path_test_CFLAGS  = $(AM_CFLAGS) -I.
+path_test_LDADD   = ../../libmurphy-common.la
diff --git a/src/common/tests/path-test.c b/src/common/tests/path-test.c
new file mode 100644 (file)
index 0000000..c2b0918
--- /dev/null
@@ -0,0 +1,50 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <murphy/common/file-utils.h>
+
+int main(int argc, char *argv[])
+{
+    int   i;
+    size_t size;
+    char *p, buf[PATH_MAX];
+    struct stat ost, nst;
+
+    if (argc > 1) {
+        size = strtoul(argv[1], &p, 10);
+        if (*p || size > sizeof(buf))
+            size = sizeof(buf);
+    }
+    else
+        size = sizeof(buf);
+
+    for (i = 1; i < argc; i++) {
+        printf("'%s':\n", argv[i]);
+        if ((p = mrp_normalize_path(buf, size, argv[i])) != NULL) {
+            printf("    -> '%s'\n", p);
+
+            if (stat(argv[i], &ost) < 0)
+                printf("    Non-existing path, can't test in practice...\n");
+            else{
+                if (stat(buf, &nst) == 0 &&
+                    ost.st_dev == nst.st_dev && ost.st_ino == nst.st_ino)
+                    printf("    Filesystem-equality check: OK.\n");
+                else {
+                    printf("    Filesystem-equality check: FAILED\n");
+                    exit(1);
+                }
+            }
+        }
+        else {
+            printf("    failed (%d: %s)\n", errno, strerror(errno));
+            exit(1);
+        }
+    }
+
+    return 0;
+}