aboutsummaryrefslogtreecommitdiff
path: root/src/common/fs/path_util.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/fs/path_util.cpp')
-rw-r--r--src/common/fs/path_util.cpp77
1 files changed, 65 insertions, 12 deletions
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index b895aac54c..82ea8f6cb2 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -5,6 +5,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
+#include <cctype>
#include <iostream>
#include <sstream>
#include "common/container/unordered_map.h"
@@ -480,28 +481,80 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se
[type2](char c1, char c2) { return c1 == type2 && c2 == type2; }),
path.end());
- const bool absolute = !path.empty() && path[0] == type2;
- std::vector<std::string_view> parts;
+ std::string root;
+ std::string_view components{path};
+ bool drive_relative = false;
+
+#ifdef _WIN32
+ const bool network = path.size() > 1 && path[0] == type2 && path[1] == type2;
+ const bool drive =
+ path.size() > 1 && std::isalpha(static_cast<unsigned char>(path[0])) && path[1] == ':';
+
+ if (network) {
+ root.assign(2, type2);
+ components.remove_prefix(2);
+ } else if (drive) {
+ root.assign(path.data(), 2);
+ components.remove_prefix(2);
+ if (!components.empty() && components.front() == type2) {
+ root += type2;
+ components.remove_prefix(1);
+ } else {
+ drive_relative = true;
+ }
+ }
+#endif
+
+ if (root.empty() && !components.empty() && components.front() == type2) {
+ root += type2;
+ components.remove_prefix(1);
+ }
+
+ const auto path_parts = SplitPathComponents(components);
+ std::size_t root_component_count = 0;
+#ifdef _WIN32
+ if (network) {
+ root_component_count = 2;
+
+ const auto is_unc = [](std::string_view part) {
+ return part.size() == 3 && (part[0] == 'U' || part[0] == 'u') &&
+ (part[1] == 'N' || part[1] == 'n') && (part[2] == 'C' || part[2] == 'c');
+ };
+ if (path_parts.size() >= 2 && path_parts[0] == "?" && is_unc(path_parts[1])) {
+ root_component_count = 4;
+ }
+ }
+#endif
- for (const auto part : SplitPathComponents(path))
- {
- if (part.empty() || part == ".")
+ std::vector<std::string_view> parts;
+ for (std::size_t i = 0; i < path_parts.size(); ++i) {
+ const auto part = path_parts[i];
+ if (i < root_component_count) {
+ parts.push_back(part);
+ } else if (part.empty() || part == ".") {
continue;
- if (part == ".." && !parts.empty() && parts.back() != "..")
- parts.pop_back();
- else if (part != "..") parts.push_back(part);
+ } else if (part == "..") {
+ if (parts.size() > root_component_count) {
+ parts.pop_back();
+ }
+ } else {
+ parts.push_back(part);
+ }
}
- std::string resolved = absolute ? std::string(1, type2) : std::string{};
- for (std::size_t i = 0; i < parts.size(); ++i)
- {
- if (i != 0)
+ const std::size_t root_length = root.size();
+ std::string resolved = std::move(root);
+ for (std::size_t i = 0; i < parts.size(); ++i) {
+ if (i != 0 || (!resolved.empty() && resolved.back() != type2 && !drive_relative))
resolved += type2;
resolved.append(parts[i].data(), parts[i].size());
}
path = std::move(resolved);
+ if (!path.empty() && path.size() == root_length) {
+ return path;
+ }
return std::string(RemoveTrailingSlash(path));
}