]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Implement Util::normalize_absolute_path
authorJoel Rosdahl <joel@rosdahl.net>
Wed, 19 Feb 2020 10:00:24 +0000 (11:00 +0100)
committerJoel Rosdahl <joel@rosdahl.net>
Sun, 23 Feb 2020 20:03:34 +0000 (21:03 +0100)
Normalization here means syntactically removing redundant slashes and
resolving "." and ".." parts. The algorithm does however *not* follow
symlinks, so the result may not actually resolve to `path`.

src/Util.cpp
src/Util.hpp
unittest/test_Util.cpp

index 246dcf67dee74c5e6cd456224cfac8b37eb3f48c..b75e3d47efcad2c649bb775433c471c51b01d213 100644 (file)
@@ -300,6 +300,66 @@ is_absolute_path(string_view path)
   return !path.empty() && path[0] == '/';
 }
 
+std::string
+normalize_absolute_path(string_view path)
+{
+  if (!is_absolute_path(path)) {
+    return std::string(path);
+  }
+
+#ifdef _WIN32
+  if (path.find("\\") != string_view::npos) {
+    std::string new_path(path);
+    std::replace(new_path.begin(), new_path.end(), '\\', '/');
+    return normalize_absolute_path(new_path);
+  }
+
+  std::string drive(path.substr(0, 2));
+  path = path.substr(2);
+#endif
+
+  std::string result = "/";
+  const size_t npos = string_view::npos;
+  size_t left = 1;
+  size_t right = 1;
+
+  while (true) {
+    if (left >= path.length()) {
+      break;
+    }
+    right = path.find('/', left);
+    string_view part = path.substr(left, right == npos ? npos : right - left);
+    if (part == "..") {
+      if (result.length() > 1) {
+        // "/x/../part" -> "/part"
+        result.erase(result.rfind('/', result.length() - 2) + 1);
+      } else {
+        // "/../part" -> "/part"
+      }
+    } else if (part == ".") {
+      // "/x/." -> "/x"
+    } else {
+      result.append(part.begin(), part.end());
+      if (result[result.length() - 1] != '/') {
+        result += '/';
+      }
+    }
+    if (right == npos) {
+      break;
+    }
+    left = right + 1;
+  }
+  if (result.length() > 1) {
+    result.erase(result.find_last_not_of('/') + 1);
+  }
+
+#ifdef _WIN32
+  return drive + result;
+#else
+  return result;
+#endif
+}
+
 int
 parse_int(const std::string& value)
 {
index bc05d42c0226e83e7f73702f9faf81cee35705d7..2797f914b69b6e4491879e3cd360f180799bee28 100644 (file)
@@ -192,6 +192,15 @@ int_to_big_endian(int8_t value, uint8_t* buffer)
 // Return whether `path` is absolute.
 bool is_absolute_path(nonstd::string_view path);
 
+// Normalize absolute path `path`, not taking symlinks into account.
+//
+// Normalization here means syntactically removing redundant slashes and
+// resolving "." and ".." parts. The algorithm does however *not* follow
+// symlinks, so the result may not actually resolve to `path`.
+//
+// On Windows: Backslashes are replaced with forward slashes.
+std::string normalize_absolute_path(nonstd::string_view path);
+
 // Parse a string into an integer.
 //
 // Throws Error on error.
index 03d5e6e7611a639fbea5de27ebd2ab5dd60fd96a..c6de9eda9068ba12fb2fe69bb7d7d06913622aba 100644 (file)
@@ -317,6 +317,36 @@ TEST_CASE("Util::is_absolute_path")
   CHECK(!Util::is_absolute_path("foo/fie"));
 }
 
+TEST_CASE("Util::normalize_absolute_path")
+{
+  CHECK(Util::normalize_absolute_path("") == "");
+  CHECK(Util::normalize_absolute_path(".") == ".");
+  CHECK(Util::normalize_absolute_path("..") == "..");
+  CHECK(Util::normalize_absolute_path("...") == "...");
+  CHECK(Util::normalize_absolute_path("x/./") == "x/./");
+
+#ifdef _WIN32
+  CHECK(Util::normalize_absolute_path("c:/") == "c:/");
+  CHECK(Util::normalize_absolute_path("c:\\") == "c:/");
+  CHECK(Util::normalize_absolute_path("c:/.") == "c:/");
+  CHECK(Util::normalize_absolute_path("c:\\..") == "c:/");
+  CHECK(Util::normalize_absolute_path("c:\\x/..") == "c:/");
+  CHECK(Util::normalize_absolute_path("c:\\x/./y\\..\\\\z") == "c:/x/z");
+#else
+  CHECK(Util::normalize_absolute_path("/") == "/");
+  CHECK(Util::normalize_absolute_path("/.") == "/");
+  CHECK(Util::normalize_absolute_path("/..") == "/");
+  CHECK(Util::normalize_absolute_path("/./") == "/");
+  CHECK(Util::normalize_absolute_path("//") == "/");
+  CHECK(Util::normalize_absolute_path("/../x") == "/x");
+  CHECK(Util::normalize_absolute_path("/x/./y/z") == "/x/y/z");
+  CHECK(Util::normalize_absolute_path("/x/../y/z/") == "/y/z");
+  CHECK(Util::normalize_absolute_path("/x/.../y/z") == "/x/.../y/z");
+  CHECK(Util::normalize_absolute_path("/x/yyy/../zz") == "/x/zz");
+  CHECK(Util::normalize_absolute_path("//x/yyy///.././zz") == "/x/zz");
+#endif
+}
+
 TEST_CASE("Util::parse_int")
 {
   CHECK(Util::parse_int("0") == 0);