Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Sanitize JoinPath handling#19923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
feichai0017 wants to merge12 commits intoduckdb:main
base:main
Choose a base branch
Loading
fromfeichai0017:sanitize-joinpath
Open
Show file tree
Hide file tree
Changes from1 commit
Commits
Show all changes
12 commits
Select commitHold shift + click to select a range
2ac6e83
Sanitize JoinPath handling and add tests
feichai0017Nov 25, 2025
6f8544c
fix code format
feichai0017Nov 25, 2025
1e86618
Merge branch 'main' into sanitize-joinpath
feichai0017Nov 25, 2025
30a31ba
Merge branch 'main' into sanitize-joinpath
feichai0017Nov 26, 2025
a0008c3
Harden JoinPath normalization and edge tests
feichai0017Nov 28, 2025
5e0d316
Merge branch 'main' into sanitize-joinpath
feichai0017Nov 28, 2025
4b263f9
Merge branch 'sanitize-joinpath' of github.com:feichai0017/duckdb int…
feichai0017Nov 28, 2025
4ccb2a7
Fix Windows drive root joins and add glob regression test
feichai0017Nov 28, 2025
13e18c6
correct coede format
feichai0017Nov 28, 2025
4a98a83
Add path_join SQL function
feichai0017Nov 29, 2025
192b8de
fix code format
feichai0017Nov 29, 2025
be48b04
Regenerate function metadata for path_join
feichai0017Nov 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
NextNext commit
Sanitize JoinPath handling and add tests
  • Loading branch information
@feichai0017
feichai0017 committedNov 25, 2025
commit2ac6e8375b6cc175033c2bd65e8f3ef9b004dc8f
116 changes: 114 additions & 2 deletionssrc/common/file_system.cpp
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -295,8 +295,120 @@ string FileSystem::GetWorkingDirectory() {
#endif

string FileSystem::JoinPath(const string &a, const string &b) {
// FIXME: sanitize paths
return a.empty() ? b : a + PathSeparator(a) + b;
auto has_uri_scheme = [](const string &path) {
if (path.empty()) {
return false;
}
if (StringUtil::StartsWith(path, "file:")) {
return true;
}
auto scheme_pos = path.find("://");
if (scheme_pos == string::npos) {
return false;
}
// avoid treating a Windows drive ("C:\") as a scheme
if (scheme_pos == 1 && StringUtil::CharacterIsAlpha(path[0])) {
return false;
}
return true;
};

auto trim_leading_separators = [](string &path, char separator) {
while (!path.empty() && path.front() == separator) {
path.erase(path.begin());
}
};

auto trim_trailing_separators = [](string &path, char separator) {
while (path.size() > 1 && path.back() == separator) {
path.pop_back();
}
};

auto normalize_segments = [](const string &path, const string &separator, bool is_absolute) {
auto parts = StringUtil::Split(path, separator);

string drive_prefix;
idx_t part_start = 0;
if (is_absolute && !parts.empty() && parts[0].find(':') != string::npos) {
// Preserve drive designator on Windows (e.g., "C:")
drive_prefix = parts[0];
part_start = 1;
}

vector<string> stack;
for (idx_t i = part_start; i < parts.size(); i++) {
const auto &part = parts[i];
if (part.empty() || part == ".") {
continue;
}
if (part == "..") {
if (!stack.empty() && stack.back() != "..") {
stack.pop_back();
} else if (!is_absolute) {
stack.push_back(part);
}
continue;
}
stack.push_back(part);
}

string result;
if (is_absolute) {
if (!drive_prefix.empty()) {
result = drive_prefix + separator;
} else {
result = separator;
}
}

for (idx_t i = 0; i < stack.size(); i++) {
if (!result.empty() && result.back() != separator[0]) {
result += separator;
}
result += stack[i];
}

if (result.empty() && !drive_prefix.empty()) {
result = drive_prefix + separator;
}
return result.empty() ? path : result;
};

auto separator = PathSeparator(!a.empty() ? a : b);
auto separator_char = separator[0];

// if either side is a URI, fall back to a simple concatenation to avoid breaking schemes
if (has_uri_scheme(a) || has_uri_scheme(b)) {
if (a.empty()) {
return b;
}
if (b.empty()) {
return a;
}
return a + separator + b;
}

auto lhs = ConvertSeparators(a);
auto rhs = ConvertSeparators(b);

if (lhs.empty()) {
return normalize_segments(rhs, separator, IsPathAbsolute(rhs));
}
if (rhs.empty()) {
return normalize_segments(lhs, separator, IsPathAbsolute(lhs));
}

// if rhs is an absolute path, prefer it
if (IsPathAbsolute(rhs)) {
return normalize_segments(rhs, PathSeparator(rhs), true);
}

trim_trailing_separators(lhs, separator_char);
trim_leading_separators(rhs, separator_char);

auto combined = lhs + separator + rhs;
return normalize_segments(combined, separator, IsPathAbsolute(lhs));
}

string FileSystem::ConvertSeparators(const string &path) {
Expand Down
19 changes: 19 additions & 0 deletionstest/common/test_file_system.cpp
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
#include "duckdb/common/file_system.hpp"
#include "duckdb/common/fstream.hpp"
#include "duckdb/common/local_file_system.hpp"
#include "duckdb/common/string_util.hpp"
#include "duckdb/common/vector.hpp"
#include "duckdb/common/virtual_file_system.hpp"
#include "test_helpers.hpp"
Expand DownExpand Up@@ -142,6 +143,24 @@ TEST_CASE("Make sure file system operators work as advertised", "[file_system]")
REQUIRE(!fs->FileExists(fname_in_dir2));
}

TEST_CASE("JoinPath normalizes separators and dot segments", "[file_system]") {
duckdb::unique_ptr<FileSystem> fs = FileSystem::CreateLocal();
auto sep = fs->PathSeparator("dummy");
auto collapse = [&](const string &path) { return StringUtil::Replace(path, "/", sep); };

auto normalized = fs->JoinPath("dir//subdir/", "./file");
REQUIRE(normalized == collapse("dir/subdir/file"));

auto parent = fs->JoinPath("dir/subdir", "../sibling");
REQUIRE(parent == collapse("dir/sibling"));

auto abs_override = fs->JoinPath("dir", "/abs/path");
REQUIRE(abs_override == fs->ConvertSeparators("/abs/path"));

auto dedup = fs->JoinPath("dir///", "nested///child");
REQUIRE(dedup == collapse("dir/nested/child"));
}

// note: the integer count is chosen as 512 so that we write 512*8=4096 bytes to the file
// this is required for the Direct-IO as on Windows Direct-IO can only write multiples of sector sizes
// sector sizes are typically one of [512/1024/2048/4096] bytes, hence a 4096 bytes write succeeds.
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp