mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-10-10 22:59:58 +00:00
[common/fs] Preserve Windows roots during path sanitization (#4437)
Commit 5b4c29b123 made SanitizePath rebuild paths from split components after resolving traversal. That reconstruction retained only one leading separator, collapsing UNC paths and breaking network-backed directories.
Parse the path root separately from normal components. Preserve UNC prefixes, drive-absolute and drive-relative forms, and extended UNC/device namespaces while normalizing separators and dot components.
Treat the UNC server/share portion as part of the root so parent traversal cannot escape into another share. Also preserve root-only results such as / and C:\\ instead of stripping their final separator.
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.
-------------------
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4437
Reviewed-by: lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
This commit is contained in:
+66
-13
@@ -5,6 +5,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include "common/container/unordered_map.h"
|
#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; }),
|
[type2](char c1, char c2) { return c1 == type2 && c2 == type2; }),
|
||||||
path.end());
|
path.end());
|
||||||
|
|
||||||
const bool absolute = !path.empty() && path[0] == type2;
|
std::string root;
|
||||||
std::vector<std::string_view> parts;
|
std::string_view components{path};
|
||||||
|
bool drive_relative = false;
|
||||||
|
|
||||||
for (const auto part : SplitPathComponents(path))
|
#ifdef _WIN32
|
||||||
{
|
const bool network = path.size() > 1 && path[0] == type2 && path[1] == type2;
|
||||||
if (part.empty() || part == ".")
|
const bool drive =
|
||||||
continue;
|
path.size() > 1 && std::isalpha(static_cast<unsigned char>(path[0])) && path[1] == ':';
|
||||||
if (part == ".." && !parts.empty() && parts.back() != "..")
|
|
||||||
parts.pop_back();
|
if (network) {
|
||||||
else if (part != "..") parts.push_back(part);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string resolved = absolute ? std::string(1, type2) : std::string{};
|
const auto path_parts = SplitPathComponents(components);
|
||||||
for (std::size_t i = 0; i < parts.size(); ++i)
|
std::size_t root_component_count = 0;
|
||||||
{
|
#ifdef _WIN32
|
||||||
if (i != 0)
|
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
|
||||||
|
|
||||||
|
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;
|
||||||
|
} else if (part == "..") {
|
||||||
|
if (parts.size() > root_component_count) {
|
||||||
|
parts.pop_back();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parts.push_back(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 += type2;
|
||||||
resolved.append(parts[i].data(), parts[i].size());
|
resolved.append(parts[i].data(), parts[i].size());
|
||||||
}
|
}
|
||||||
|
|
||||||
path = std::move(resolved);
|
path = std::move(resolved);
|
||||||
|
|
||||||
|
if (!path.empty() && path.size() == root_length) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
return std::string(RemoveTrailingSlash(path));
|
return std::string(RemoveTrailingSlash(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -347,8 +347,9 @@ enum class DirectorySeparator {
|
|||||||
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
|
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
|
||||||
[[nodiscard]] std::vector<std::string> SplitPathComponentsCopy(std::string_view filename);
|
[[nodiscard]] std::vector<std::string> SplitPathComponentsCopy(std::string_view filename);
|
||||||
|
|
||||||
// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
|
// Normalizes directory separators, removes duplicate and non-root trailing separators, and resolves
|
||||||
// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
|
// '.' and '..' components without traversing above the path root. Windows drive and UNC roots are
|
||||||
|
// preserved.
|
||||||
[[nodiscard]] std::string SanitizePath(
|
[[nodiscard]] std::string SanitizePath(
|
||||||
std::string_view path,
|
std::string_view path,
|
||||||
DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
|
DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
|
||||||
|
|||||||
Reference in New Issue
Block a user