]> granicus.if.org Git - neomutt/commitdiff
add path manipulation functions
authorRichard Russon <rich@flatcap.org>
Wed, 15 Aug 2018 10:56:43 +0000 (11:56 +0100)
committerRichard Russon <rich@flatcap.org>
Wed, 15 Aug 2018 10:56:43 +0000 (11:56 +0100)
Makefile.autosetup
mutt/path.c [new file with mode: 0644]
mutt/path.h [new file with mode: 0644]
test/Makefile.autosetup
test/main.c
test/path.c [new file with mode: 0644]

index 7e99cc4da6caa852c85b1254cf621aa1e2b20008..8367548fcdca5e8c270abf4c93347c6999040e2d 100644 (file)
@@ -248,8 +248,8 @@ LIBMUTT=    libmutt.a
 LIBMUTTOBJS=   mutt/base64.o mutt/buffer.o mutt/charset.o mutt/date.o \
                mutt/envlist.o mutt/exit.o mutt/file.o mutt/hash.o \
                mutt/history.o mutt/list.o mutt/logging.o mutt/mapping.o \
-               mutt/mbyte.o mutt/md5.o mutt/memory.o mutt/regex.o mutt/sha1.o \
-               mutt/signal.o mutt/string.o
+               mutt/mbyte.o mutt/md5.o mutt/memory.o mutt/path.o mutt/regex.o \
+               mutt/sha1.o mutt/signal.o mutt/string.o
 CLEANFILES+=   $(LIBMUTT) $(LIBMUTTOBJS)
 MUTTLIBS+=     $(LIBMUTT)
 ALLOBJS+=      $(LIBMUTTOBJS)
diff --git a/mutt/path.c b/mutt/path.c
new file mode 100644 (file)
index 0000000..43be8e1
--- /dev/null
@@ -0,0 +1,163 @@
+/**
+ * @file
+ * Path manipulation functions
+ *
+ * @authors
+ * Copyright (C) 2018 Richard Russon <rich@flatcap.org>
+ *
+ * @copyright
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include <limits.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "mutt/mutt.h"
+#include "email/email.h"
+#include "alias.h"
+#include "globals.h"
+#include "hook.h"
+#include "imap/imap.h"
+#include "mx.h"
+
+/**
+ * mutt_path_tidy_slash - Remove unnecessary slashes and dots
+ * @param[in,out] buf Path to modify
+ * @retval true Success
+ *
+ * Collapse repeated '//' and '/./'
+ */
+bool mutt_path_tidy_slash(char *buf)
+{
+  if (!buf)
+    return false;
+
+  char *r = buf;
+  char *w = buf;
+
+  while (*r != '\0')
+  {
+    *w++ = *r++;
+
+    if (r[-1] == '/')
+    {
+      for (; (r[0] == '/') || (r[0] == '.'); r++)
+      {
+        if (r[0] == '/')
+          continue;
+        if (r[0] == '.')
+        {
+          if (r[1] == '/')
+          {
+            r++;
+            continue;
+          }
+          else if (r[1] == '\0')
+          {
+            r[0] = '\0';
+          }
+          break;
+        }
+      }
+    }
+  }
+
+  if ((w[-1] == '/') && (w != (buf + 1)))
+    w--;
+
+  *w = '\0';
+
+  return true;
+}
+
+/**
+ * mutt_path_tidy_dotdot - Remove dot-dot-slash from a path
+ * @param[in,out] buf Path to modify
+ * @retval true Success
+ *
+ * Collapse dot-dot patterns, like '/dir/../'
+ */
+bool mutt_path_tidy_dotdot(char *buf)
+{
+  if (!buf || (buf[0] != '/'))
+    return false;
+
+  char *dd = buf;
+
+  mutt_debug(3, "Collapse path: %s\n", buf);
+  while ((dd = strstr(dd, "/..")))
+  {
+    if (dd[3] == '/') /* paths follow dots */
+    {
+      char *dest = NULL;
+      if (dd != buf) /* not at start of string */
+      {
+        dd[0] = '\0';
+        dest = strrchr(buf, '/');
+      }
+      if (!dest)
+        dest = buf;
+
+      memmove(dest, dd + 3, strlen(dd + 3) + 1);
+    }
+    else if (dd[3] == '\0') /* dots at end of string */
+    {
+      if (dd == buf) /* at start of string */
+      {
+        dd[1] = '\0';
+      }
+      else
+      {
+        dd[0] = '\0';
+        char *s = strrchr(buf, '/');
+        if (s == buf)
+          s[1] = '\0';
+        else if (s)
+          s[0] = '\0';
+      }
+    }
+    else
+    {
+      dd += 3; /* skip over '/..dir/' */
+      continue;
+    }
+
+    dd = buf; /* restart at the beginning */
+  }
+
+  mutt_debug(3, "Collapsed to:  %s\n", buf);
+  return true;
+}
+
+/**
+ * mutt_path_tidy - Remove unnecessary parts of a path
+ * @param[in,out] buf Path to modify
+ * @retval true Success
+ *
+ * Remove unnecessary dots and slashes from a path
+ */
+bool mutt_path_tidy(char *buf)
+{
+  if (!buf || (buf[0] != '/'))
+    return false;
+
+  if (!mutt_path_tidy_slash(buf))
+    return false;
+
+  return mutt_path_tidy_dotdot(buf);
+}
diff --git a/mutt/path.h b/mutt/path.h
new file mode 100644 (file)
index 0000000..df52e97
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * @file
+ * Path manipulation functions
+ *
+ * @authors
+ * Copyright (C) 2017 Richard Russon <rich@flatcap.org>
+ *
+ * @copyright
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _MUTT_PATH_H
+#define _MUTT_PATH_H
+
+#include <stdbool.h>
+#include <stdio.h>
+
+bool mutt_path_tidy       (char *buf);
+bool mutt_path_tidy_dotdot(char *buf);
+bool mutt_path_tidy_slash (char *buf);
+
+#endif /* _MUTT_PATH_H */
index b3fb7247e4f4a2abd744da3b25e4638be5f9d516..a8cba97ee9f4badaf148d7c0bb537a3d150cc78c 100644 (file)
@@ -1,6 +1,7 @@
 TEST_OBJS   = test/main.o \
              test/base64.o \
              test/md5.o \
+             test/path.o \
              test/rfc2047.o \
              test/string.o \
              test/address.o
index f64835159e28d3b0e7f4030a30fa76a5c7a27f6c..ea1c620f61afe7feecc6ac0e6606cc2a1134b2df 100644 (file)
   NEOMUTT_TEST_ITEM(test_md5_ctx_bytes)                                        \
   NEOMUTT_TEST_ITEM(test_string_strfcpy)                                       \
   NEOMUTT_TEST_ITEM(test_string_strnfcpy)                                      \
-  NEOMUTT_TEST_ITEM(test_addr_mbox_to_udomain)
+  NEOMUTT_TEST_ITEM(test_addr_mbox_to_udomain)                                 \
+  NEOMUTT_TEST_ITEM(test_mutt_path_tidy_slash)                                 \
+  NEOMUTT_TEST_ITEM(test_mutt_path_tidy_dotdot)                                \
+  NEOMUTT_TEST_ITEM(test_mutt_path_tidy)
 
 /******************************************************************************
  * You probably don't need to touch what follows.
diff --git a/test/path.c b/test/path.c
new file mode 100644 (file)
index 0000000..358dd46
--- /dev/null
@@ -0,0 +1,227 @@
+#define TEST_NO_MAIN
+#include "acutest.h"
+#include "mutt/memory.h"
+#include "mutt/path.h"
+#include "mutt/string2.h"
+
+void test_mutt_path_tidy_slash(void)
+{
+  static const char *tests[][2] =
+  {
+    { NULL,                     NULL,            },
+    { "/",                      "/",             },
+    { "//",                     "/",             },
+    { "///",                    "/",             },
+    { "/apple/",                "/apple",        },
+    { "/apple//",               "/apple",        },
+    { "/apple///",              "/apple",        },
+    { "/apple/banana",          "/apple/banana", },
+    { "/apple//banana",         "/apple/banana", },
+    { "/apple///banana",        "/apple/banana", },
+    { "/apple/banana/",         "/apple/banana", },
+    { "/apple/banana//",        "/apple/banana", },
+    { "/apple/banana///",       "/apple/banana", },
+    { "//.///././apple/banana", "/apple/banana", },
+    { "/apple/.///././banana",  "/apple/banana", },
+    { "/apple/banana/.///././", "/apple/banana", },
+    { "/apple/banana/",         "/apple/banana", },
+    { "/apple/banana/.",        "/apple/banana", },
+    { "/apple/banana/./",       "/apple/banana", },
+    { "/apple/banana//",        "/apple/banana", },
+    { "/apple/banana//.",       "/apple/banana", },
+    { "/apple/banana//./",      "/apple/banana", },
+    { "////apple/banana",       "/apple/banana", },
+    { "/.//apple/banana",       "/apple/banana", },
+  };
+
+  char buf[64];
+  for (size_t i = 0; i < mutt_array_size(tests); i++)
+  {
+    mutt_str_strfcpy(buf, tests[i][0], sizeof(buf));
+    mutt_path_tidy_slash(buf);
+    if (!TEST_CHECK(mutt_str_strcmp(buf, tests[i][1]) == 0))
+    {
+      TEST_MSG("Input:    %s", tests[i][0]);
+      TEST_MSG("Expected: %s", tests[i][1]);
+      TEST_MSG("Actual:   %s", buf);
+    }
+  }
+}
+
+void test_mutt_path_tidy_dotdot(void)
+{
+  static const char *tests[][2] =
+  {
+    { NULL,                                   NULL,                          },
+    { "/",                                    "/",                           },
+    { "/apple",                               "/apple",                      },
+    { "/apple/banana",                        "/apple/banana",               },
+    { "/..",                                  "/",                           },
+    { "/apple/..",                            "/",                           },
+    { "/apple/banana/..",                     "/apple",                      },
+    { "/../cherry",                           "/cherry",                     },
+    { "/apple/../cherry",                     "/cherry",                     },
+    { "/apple/banana/../cherry",              "/apple/cherry",               },
+    { "/apple/..",                            "/",                           },
+    { "/apple/../..",                         "/",                           },
+    { "/apple/../../..",                      "/",                           },
+    { "/apple/../../../..",                   "/",                           },
+    { "/apple/banana/..",                     "/apple",                      },
+    { "/apple/banana/../..",                  "/",                           },
+    { "/apple/banana/../../..",               "/",                           },
+    { "/apple/banana/../../../..",            "/",                           },
+    { "/../apple",                            "/apple",                      },
+    { "/../../apple",                         "/apple",                      },
+    { "/../../../apple",                      "/apple",                      },
+    { "/../apple/banana/cherry/damson",       "/apple/banana/cherry/damson", },
+    { "/apple/../banana/cherry/damson",       "/banana/cherry/damson",       },
+    { "/apple/banana/../cherry/damson",       "/apple/cherry/damson",        },
+    { "/apple/banana/cherry/../damson",       "/apple/banana/damson",        },
+    { "/apple/banana/cherry/damson/..",       "/apple/banana/cherry",        },
+    { "/../../apple/banana/cherry/damson",    "/apple/banana/cherry/damson", },
+    { "/apple/../../banana/cherry/damson",    "/banana/cherry/damson",       },
+    { "/apple/banana/../../cherry/damson",    "/cherry/damson",              },
+    { "/apple/banana/cherry/../../damson",    "/apple/damson",               },
+    { "/apple/banana/cherry/damson/../..",    "/apple/banana",               },
+    { "/../apple/../banana/cherry/damson",    "/banana/cherry/damson",       },
+    { "/apple/../banana/../cherry/damson",    "/cherry/damson",              },
+    { "/apple/banana/../cherry/../damson",    "/apple/damson",               },
+    { "/apple/banana/cherry/../damson/..",    "/apple/banana",               },
+    { "/apple/..banana/cherry/../damson",     "/apple/..banana/damson",      },
+    { "/..apple/..banana/..cherry/../damson", "/..apple/..banana/damson",    },
+  };
+
+  char buf[64];
+  for (size_t i = 0; i < mutt_array_size(tests); i++)
+  {
+    mutt_str_strfcpy(buf, tests[i][0], sizeof(buf));
+    mutt_path_tidy_dotdot(buf);
+    if (!TEST_CHECK(mutt_str_strcmp(buf, tests[i][1]) == 0))
+    {
+      TEST_MSG("Input:    %s", tests[i][0]);
+      TEST_MSG("Expected: %s", tests[i][1]);
+      TEST_MSG("Actual:   %s", buf);
+    }
+  }
+}
+
+void test_mutt_path_tidy(void)
+{
+  static const char *tests[][2] =
+  {
+    { "/..apple/./../////./banana/banana/./banana/..apple/./banana/..apple/banana///banana/..apple/banana/..apple/banana/banana/..apple",                                     "/banana/banana/banana/..apple/banana/..apple/banana/banana/..apple/banana/..apple/banana/banana/..apple",                                      },
+    { "/../../banana///..apple///..apple///banana///banana/banana/banana/..apple/banana/banana/banana/./banana/banana/banana/..apple/banana",                                 "/banana/..apple/..apple/banana/banana/banana/banana/..apple/banana/banana/banana/banana/banana/banana/..apple/banana",                         },
+    { "///banana/banana/banana/./..apple/../banana/..apple/../..apple/./banana/./..apple",                                                                                    "/banana/banana/banana/banana/..apple/banana/..apple",                                                                                          },
+    { "/./banana/banana/../banana/banana/.///banana/..apple/..apple",                                                                                                         "/banana/banana/banana/banana/..apple/..apple",                                                                                                 },
+    { "/../banana/banana/banana/banana///..apple///..apple/banana/banana/////./..apple/./../.././banana/banana///banana/banana",                                              "/banana/banana/banana/banana/..apple/..apple/banana/banana/banana/banana/banana",                                                              },
+    { "/banana/banana/./././..apple/banana///./banana/banana/banana/banana/banana/banana/../////banana/banana/banana/./..apple/..apple/..///..apple",                         "/banana/banana/..apple/banana/banana/banana/banana/banana/banana/banana/banana/banana/..apple/..apple",                                        },
+    { "/banana///..apple///../banana/banana/banana///////banana/banana/./..apple/..apple/./..apple/..apple/banana",                                                           "/banana/banana/banana/banana/banana/banana/..apple/..apple/..apple/..apple/banana",                                                            },
+    { "/banana/..apple/..apple/..apple/..apple/banana///../..apple///banana/banana/banana/banana///./../..apple/../banana/..apple/../banana/banana/./..apple",                "/banana/..apple/..apple/..apple/..apple/..apple/banana/banana/banana/banana/banana/banana/..apple",                                            },
+    { "/banana/banana/..///../banana/../banana/banana/..apple/./../banana/../../banana/.",                                                                                    "/banana/banana",                                                                                                                               },
+    { "/banana/banana/../..apple/banana/././banana///banana/banana",                                                                                                          "/banana/..apple/banana/banana/banana/banana",                                                                                                  },
+    { "/////banana/banana/banana///..apple/./banana/..apple/./banana/banana",                                                                                                 "/banana/banana/banana/..apple/banana/..apple/banana/banana",                                                                                   },
+    { "/..apple/..apple/banana///banana/././//.///./banana///./banana/..apple/./banana",                                                                                      "/..apple/..apple/banana/banana/banana/banana/..apple/banana",                                                                                  },
+    { "///./..apple/banana/./../banana/././..apple///./../../../////banana/banana/../..apple/banana/banana/../banana/banana/../.",                                            "/banana/..apple/banana/banana",                                                                                                                },
+    { "/banana/./../././../..apple/banana/banana/..///../.",                                                                                                                  "/..apple",                                                                                                                                     },
+    { "/./..apple/banana///./banana/..///../banana//",                                                                                                                        "/..apple/banana",                                                                                                                              },
+    { "/.///banana///..apple/banana/banana/../.././banana/../..apple///banana/banana/./banana/banana/..//",                                                                   "/banana/..apple/..apple/banana/banana/banana",                                                                                                 },
+    { "/..apple/..apple/../banana/banana/..apple/./banana/../banana///banana",                                                                                                "/..apple/banana/banana/..apple/banana/banana",                                                                                                 },
+    { "/banana/banana/../././banana/banana/banana///./.././//banana/banana/banana/.././banana///..apple/banana//",                                                            "/banana/banana/banana/banana/banana/banana/..apple/banana",                                                                                    },
+    { "/banana/banana/../banana/./banana/banana/banana/..apple/../banana/.///banana/////../..apple/banana/banana/../..apple/banana/banana/banana///banana",                   "/banana/banana/banana/banana/banana/banana/..apple/banana/..apple/banana/banana/banana/banana",                                                },
+    { "/./..apple/./banana///banana/./banana/..apple/banana///.///././banana",                                                                                                "/..apple/banana/banana/banana/..apple/banana/banana",                                                                                          },
+    { "/./banana/..apple/banana/banana/.././.././..apple/banana/banana/..apple/.///..apple/.///banana/banana/..",                                                             "/banana/..apple/..apple/banana/banana/..apple/..apple/banana",                                                                                 },
+    { "///./../..apple/banana/../banana///banana///..///..apple/../banana/../../banana/..apple/./banana/..apple/banana/..apple/banana//",                                     "/..apple/banana/..apple/banana/..apple/banana/..apple/banana",                                                                                 },
+    { "/banana/../..apple/banana///////banana/banana/..apple/../banana/../..",                                                                                                "/..apple/banana/banana",                                                                                                                       },
+    { "/../banana/..apple///banana/banana/..apple/..apple///banana/banana/banana///..apple/banana///../././banana/banana/banana/banana/banana/banana",                        "/banana/..apple/banana/banana/..apple/..apple/banana/banana/banana/..apple/banana/banana/banana/banana/banana/banana",                         },
+    { "///..apple///.././banana/./..apple///..apple/..",                                                                                                                      "/banana/..apple",                                                                                                                              },
+    { "///../..apple/./../..apple/banana/banana///..apple/banana///../banana/banana",                                                                                         "/..apple/banana/banana/..apple/banana/banana",                                                                                                 },
+    { "/../banana/banana/banana/./banana/banana/banana///banana/banana/./banana/.",                                                                                           "/banana/banana/banana/banana/banana/banana/banana/banana/banana",                                                                              },
+    { "/././..apple/./..apple/../banana/./..apple/banana///.././banana/banana/..",                                                                                            "/..apple/banana/..apple/banana",                                                                                                               },
+    { "/..apple/..apple///banana/banana/..apple/////banana/banana/..apple///./../banana/banana/banana///banana/..apple/banana/..apple////",                                   "/..apple/..apple/banana/banana/..apple/banana/banana/banana/banana/banana/banana/..apple/banana/..apple",                                      },
+    { "/..apple/banana/./banana/banana/banana/./banana/banana/../banana/../..///..apple/banana/./.././..///././../..apple/../banana/banana//",                                "/..apple/banana/banana/banana/banana/banana",                                                                                                  },
+    { "/banana///../banana/../././..apple/..apple///.///banana/./banana/banana///banana/..apple/.",                                                                           "/..apple/..apple/banana/banana/banana/banana/..apple",                                                                                         },
+    { "/////..apple/banana/banana/..apple/banana///banana//",                                                                                                                 "/..apple/banana/banana/..apple/banana/banana",                                                                                                 },
+    { "/..apple///./banana///../../../..apple/..apple/..apple/./banana/banana",                                                                                               "/..apple/..apple/..apple/banana/banana",                                                                                                       },
+    { "///banana///././..apple/banana/banana/././..apple/..apple/..apple/banana///././banana/././banana/..apple/banana/banana/../banana/./banana",                            "/banana/..apple/banana/banana/..apple/..apple/..apple/banana/banana/banana/..apple/banana/banana/banana",                                      },
+    { "/banana///./banana/banana/..///./banana//",                                                                                                                            "/banana/banana/banana",                                                                                                                        },
+    { "/banana/////banana/banana/..apple/..apple/////.///..///..apple/banana/banana/..apple/..apple///./banana",                                                              "/banana/banana/banana/..apple/..apple/banana/banana/..apple/..apple/banana",                                                                   },
+    { "/..apple/banana///../..apple/////./..apple/./././banana/..apple",                                                                                                      "/..apple/..apple/..apple/banana/..apple",                                                                                                      },
+    { "/banana/banana///banana/../../../..apple/banana///..apple/..apple/../.././banana/..apple/..apple/..///../../..",                                                       "/..apple",                                                                                                                                     },
+    { "/..apple/./././../banana/..apple/banana/banana/////./..//",                                                                                                            "/banana/..apple/banana",                                                                                                                       },
+    { "/../..apple/banana/..apple/banana/.././////banana/../banana/banana/..apple/..apple/banana/banana",                                                                     "/..apple/banana/..apple/banana/banana/..apple/..apple/banana/banana",                                                                          },
+    { "/..apple/..apple/..apple///banana/banana/../banana/banana/banana/banana/banana/banana/..apple/.///./banana/./..apple/..apple/./..apple/banana/banana/banana/banana/.", "/..apple/..apple/..apple/banana/banana/banana/banana/banana/banana/banana/..apple/banana/..apple/..apple/..apple/banana/banana/banana/banana", },
+    { "///..///banana///../..apple/..apple/.///banana/banana/..apple/..apple/banana/././..///banana",                                                                         "/..apple/..apple/banana/banana/..apple/..apple/banana",                                                                                        },
+    { "/banana///banana/..apple/banana/..///.././..apple/banana///banana/banana/..apple///./..apple",                                                                         "/banana/banana/..apple/banana/banana/banana/..apple/..apple",                                                                                  },
+    { "/banana/banana///.././banana/./banana/..apple/.././banana/../banana/////../banana/./banana/../..apple/banana/../banana/./..",                                          "/banana/banana/banana/banana/..apple",                                                                                                         },
+    { "/banana/..apple/..apple/.././//banana/banana///.////",                                                                                                                 "/banana/..apple/banana/banana",                                                                                                                },
+    { "/banana/.././banana/banana/banana/.///../banana/..",                                                                                                                   "/banana/banana",                                                                                                                               },
+    { "/banana/.///..apple/../banana/banana/banana/../..apple///./banana/banana///./.",                                                                                       "/banana/banana/banana/..apple/banana/banana",                                                                                                  },
+    { "/..apple/..apple///../..apple/..apple/banana/banana/////../banana/banana/////../banana/./.././banana/..apple",                                                         "/..apple/..apple/..apple/banana/banana/banana/..apple",                                                                                        },
+    { "/./../banana/banana///banana/////./..apple/./..apple/../././..apple///banana",                                                                                         "/banana/banana/banana/..apple/..apple/banana",                                                                                                 },
+    { "/..///banana/../banana/./..apple/..apple///././banana",                                                                                                                "/banana/..apple/..apple/banana",                                                                                                               },
+    { "/banana/banana/banana/banana/banana/banana/banana/../banana/banana/banana/banana/banana/banana/..apple/../..apple/..apple",                                            "/banana/banana/banana/banana/banana/banana/banana/banana/banana/banana/banana/banana/..apple/..apple",                                         },
+    { "/banana/.././banana/..///banana/..apple/banana/banana/..apple",                                                                                                        "/banana/..apple/banana/banana/..apple",                                                                                                        },
+    { "/../banana/banana/../..///..apple/banana/..apple/../../..apple/banana/..apple/../banana/..apple/banana/..apple///../banana/banana/banana/../banana/..apple/banana/.",  "/..apple/..apple/banana/banana/..apple/banana/banana/banana/banana/..apple/banana",                                                            },
+    { "/banana/banana/..apple/./banana/./././banana/..apple/////..apple/banana/banana/banana////",                                                                            "/banana/banana/..apple/banana/banana/..apple/..apple/banana/banana/banana",                                                                    },
+    { "/..apple/banana/banana/../banana/banana/../..apple/banana/banana/./..",                                                                                                "/..apple/banana/banana/..apple/banana",                                                                                                        },
+    { "/.///..apple/banana/banana/banana/../banana/banana///banana/banana///banana/banana/./..apple/..///banana/..apple/banana/banana///../banana/..apple/banana",            "/..apple/banana/banana/banana/banana/banana/banana/banana/banana/banana/..apple/banana/banana/..apple/banana",                                 },
+    { "/.///./../../banana/../banana///banana/banana///banana///banana///banana",                                                                                             "/banana/banana/banana/banana/banana/banana",                                                                                                   },
+    { "/banana/banana/./banana/../../../banana/././..apple/.././banana///..apple/../.",                                                                                       "/banana/banana",                                                                                                                               },
+    { "///./../.././../../..apple/banana/..apple/..apple/banana///banana/..apple///../banana/../banana/././..apple/../..apple/./banana/.",                                    "/..apple/banana/..apple/..apple/banana/banana/banana/..apple/banana",                                                                          },
+    { "/./../banana/banana///../banana/..apple/../../banana/banana/banana/banana/banana/../////banana/./banana//",                                                            "/banana/banana/banana/banana/banana/banana/banana",                                                                                            },
+    { "/banana/./../.././../../banana/../../..apple///.///banana/banana/..apple/./banana/banana/banana/./banana/..apple/banana/..apple",                                      "/..apple/banana/banana/..apple/banana/banana/banana/banana/..apple/banana/..apple",                                                            },
+    { "/..apple/.././banana/banana/banana/../../././//../../..apple/banana///../..apple/banana/././..apple///././banana",                                                     "/..apple/..apple/banana/..apple/banana",                                                                                                       },
+    { "///../banana/.././banana/../..apple///banana/./../../..apple",                                                                                                         "/..apple",                                                                                                                                     },
+    { "/banana/banana/banana/////../..apple/banana/////./banana///banana/..apple/banana/..apple/banana/.///banana/../../..",                                                  "/banana/banana/..apple/banana/banana/banana/..apple/banana",                                                                                   },
+    { "///banana/banana/banana/..apple/banana/./..apple///./..apple/.",                                                                                                       "/banana/banana/banana/..apple/banana/..apple/..apple",                                                                                         },
+    { "/./././banana/././banana///../////../banana/./../////../banana///..apple///..apple/./.././banana/..apple//",                                                           "/banana/..apple/banana/..apple",                                                                                                               },
+    { "/banana/..apple/./../..apple/..apple/banana///./.././banana/./../..apple/banana/banana",                                                                               "/banana/..apple/..apple/..apple/banana/banana",                                                                                                },
+    { "/..apple/..apple/..apple///////banana/banana/banana/banana/////./banana/banana/./banana///../.",                                                                       "/..apple/..apple/..apple/banana/banana/banana/banana/banana/banana",                                                                           },
+    { "/..apple/../..apple///////banana/./..apple/./banana/../..apple/../../banana/banana///banana/banana/./..///.././..",                                                    "/..apple/banana/banana",                                                                                                                       },
+    { "/./.././////banana/banana/..apple/././banana/banana/banana///./.",                                                                                                     "/banana/banana/..apple/banana/banana/banana",                                                                                                  },
+    { "/banana/./../banana///././..apple/////banana///..///banana/banana///..apple",                                                                                          "/banana/..apple/banana/banana/..apple",                                                                                                        },
+    { "/banana/../banana/../////..apple/banana///./////banana/./..apple/..apple///banana///banana/../banana///banana/..apple",                                                "/..apple/banana/banana/..apple/..apple/banana/banana/banana/..apple",                                                                          },
+    { "/banana/banana/..apple/banana/./banana/banana/../banana///.",                                                                                                          "/banana/banana/..apple/banana/banana/banana",                                                                                                  },
+    { "/..apple/..apple///./banana/./..apple/../..apple/./../banana/banana/..apple/././banana/..apple/////../../banana",                                                      "/..apple/..apple/banana/banana/banana/..apple/banana",                                                                                         },
+    { "/..apple/..///banana///..apple/../banana/../..",                                                                                                                       "/",                                                                                                                                            },
+    { "/banana///banana/banana/./banana/../../..apple/./banana/banana/.././//banana/..apple/..apple/banana/banana/.///banana/./banana/..///../..",                            "/banana/banana/..apple/banana/banana/..apple/..apple/banana",                                                                                  },
+    { "/..apple/banana/..apple/.././//./..///banana///banana///../..///banana///..apple///.././../banana/../../.",                                                            "/",                                                                                                                                            },
+    { "/./banana/..apple/banana/..///./banana/../../.././../../banana/banana/banana/../..apple/banana/banana/..apple/banana/banana/.",                                        "/banana/banana/..apple/banana/banana/..apple/banana/banana",                                                                                   },
+    { "/../banana/banana/banana/..apple/..///./banana/..apple///../..apple/././../..apple/banana/./.././..//",                                                                "/banana/banana/banana/banana",                                                                                                                 },
+    { "///banana///../../banana///.././//../banana/banana/..apple/banana///banana/banana/banana/..apple/..",                                                                  "/banana/banana/..apple/banana/banana/banana/banana",                                                                                           },
+    { "/banana/../banana/././banana/..apple/./..apple///../..apple/.././////banana/./..apple/./banana",                                                                       "/banana/banana/..apple/banana/..apple/banana",                                                                                                 },
+    { "/banana/./..apple/../..apple/./banana/..apple/../banana/banana/banana/banana/banana/banana/banana",                                                                    "/banana/..apple/banana/banana/banana/banana/banana/banana/banana/banana",                                                                      },
+    { "/.././..apple///banana///..apple///banana/banana/banana/..apple/banana/./banana/.././banana/././/",                                                                    "/..apple/banana/..apple/banana/banana/banana/..apple/banana/banana",                                                                           },
+    { "///././../banana/./../../..apple/banana/banana/..apple/banana/../..apple/..apple/./banana/./banana/..apple///banana/./..apple/banana///banana",                        "/..apple/banana/banana/..apple/..apple/..apple/banana/banana/..apple/banana/..apple/banana/banana",                                            },
+    { "/..apple/banana/banana/banana///banana/..///./..apple/banana/banana/..apple/banana///.///../banana/..apple",                                                           "/..apple/banana/banana/banana/..apple/banana/banana/..apple/banana/..apple",                                                                   },
+    { "/../..apple/banana/../banana/banana/banana/banana///..apple/./..apple/../..apple/..",                                                                                  "/..apple/banana/banana/banana/banana/..apple",                                                                                                 },
+    { "/../banana/banana/banana/..apple/banana/../banana/banana/../../../..apple///banana/../banana",                                                                         "/banana/banana/banana/..apple/banana",                                                                                                         },
+    { "/banana/..apple/..apple/../banana/banana/////../././banana/banana/..apple/..apple/.",                                                                                  "/banana/..apple/banana/banana/banana/..apple/..apple",                                                                                         },
+    { "/././//banana/banana/..apple/./banana/./banana///..apple/..",                                                                                                          "/banana/banana/..apple/banana/banana",                                                                                                         },
+    { "/../banana/banana///./..apple/banana/banana///.././banana/banana/.///./banana/banana/banana/banana",                                                                   "/banana/banana/..apple/banana/banana/banana/banana/banana/banana/banana",                                                                      },
+    { "/banana/banana/banana/..apple/./././../..apple/banana/..apple/..apple/.///.././..",                                                                                    "/banana/banana/banana/..apple/banana",                                                                                                         },
+    { "///..apple/./..apple/..apple/banana/banana/banana/../////.//",                                                                                                         "/..apple/..apple/..apple/banana/banana",                                                                                                       },
+    { "/../banana/../../..apple/..apple///..apple/././banana/./banana/..apple///./..apple/./banana/banana/banana/./.././banana/../..",                                        "/..apple/..apple/..apple/banana/banana/..apple/..apple/banana",                                                                                },
+    { "/..apple/..apple/banana///..apple///..apple/..apple/banana/.././banana/..apple/././..apple/../..apple///..apple///..apple/banana/../banana/..apple/////banana",        "/..apple/..apple/banana/..apple/..apple/..apple/banana/..apple/..apple/..apple/..apple/banana/..apple/banana",                                 },
+    { "/../..apple/././banana///../..apple/banana/../.././////banana/banana/../..apple",                                                                                      "/..apple/banana/..apple",                                                                                                                      },
+    { "/banana/..apple/banana/banana///..apple/banana/../banana/.././/",                                                                                                      "/banana/..apple/banana/banana/..apple",                                                                                                        },
+    { "/..apple/banana/banana/banana/./banana/../banana/banana///..apple/banana/..///..///.",                                                                                 "/..apple/banana/banana/banana/banana/banana",                                                                                                  },
+    { "/..apple/banana/banana/.././banana/..apple/banana/..apple/..apple/../..///..apple///banana/banana/banana///banana/..apple/banana/banana",                              "/..apple/banana/banana/..apple/banana/..apple/banana/banana/banana/banana/..apple/banana/banana",                                              },
+    { "/./banana///../banana/banana/./../..apple/banana/../../banana///banana/..apple/..apple/////..",                                                                        "/banana/banana/banana/..apple",                                                                                                                },
+    { "/banana/..apple/banana///banana///./..apple/banana/banana/banana/..apple/banana/banana//",                                                                             "/banana/..apple/banana/banana/..apple/banana/banana/banana/..apple/banana/banana",                                                             },
+  };
+
+  char buf[192];
+  for (size_t i = 0; i < mutt_array_size(tests); i++)
+  {
+    mutt_str_strfcpy(buf, tests[i][0], sizeof(buf));
+    mutt_path_tidy(buf);
+    if (!TEST_CHECK(mutt_str_strcmp(buf, tests[i][1]) == 0))
+    {
+      TEST_MSG("Input:    %s", tests[i][0]);
+      TEST_MSG("Expected: %s", tests[i][1]);
+      TEST_MSG("Actual:   %s", buf);
+    }
+  }
+}
+