diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 050a6a2a7..6a9159aeb 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -4668,7 +4668,15 @@ void test_normalize_path() { do_test(normalize_path(L"") == L"."); do_test(normalize_path(L"..") == L".."); do_test(normalize_path(L"./") == L"."); - do_test(normalize_path(L"////abc") == L"//abc"); + do_test(normalize_path(L"./.") == L"."); + do_test(normalize_path(L"/") == L"/"); + do_test(normalize_path(L"//") == L"//"); + do_test(normalize_path(L"///") == L"/"); + do_test(normalize_path(L"////") == L"/"); + do_test(normalize_path(L"/.///") == L"/"); + do_test(normalize_path(L".//") == L"."); + do_test(normalize_path(L"/.//../") == L"/"); + do_test(normalize_path(L"////abc") == L"/abc"); do_test(normalize_path(L"/abc") == L"/abc"); do_test(normalize_path(L"/abc/") == L"/abc"); do_test(normalize_path(L"/abc/..def/") == L"/abc/..def"); @@ -4676,6 +4684,9 @@ void test_normalize_path() { do_test(normalize_path(L"abc/../abc/../abc/../abc") == L"abc"); do_test(normalize_path(L"../../") == L"../.."); do_test(normalize_path(L"foo/./bar") == L"foo/bar"); + do_test(normalize_path(L"foo/../") == L"."); + do_test(normalize_path(L"foo/../foo") == L"foo"); + do_test(normalize_path(L"foo/../foo/") == L"foo"); do_test(normalize_path(L"foo/././bar/.././baz") == L"foo/baz"); } diff --git a/src/wutil.cpp b/src/wutil.cpp index 4a180fca2..2a9762752 100644 --- a/src/wutil.cpp +++ b/src/wutil.cpp @@ -426,7 +426,6 @@ maybe_t wrealpath(const wcstring &pathname) { wcstring normalize_path(const wcstring &path) { // Count the leading slashes. - // Preserve up to 2. const wchar_t sep = L'/'; size_t leading_slashes = 0; for (wchar_t c : path) { @@ -439,23 +438,23 @@ wcstring normalize_path(const wcstring &path) { for (wcstring &comp : comps) { if (comp.empty() || comp == L".") { continue; - } else if (comp == L"..") { - if (new_comps.empty() || new_comps.back() == L"..") { - // We underflowed the ..s, retain this component. - new_comps.push_back(L".."); - } else { - new_comps.pop_back(); - } - } else { + } else if (comp != L"..") { new_comps.push_back(std::move(comp)); + } else if (!new_comps.empty() && new_comps.back() != L"..") { + // '..' with a real path component, drop that path component. + new_comps.pop_back(); + } else if (leading_slashes == 0) { + // We underflowed the .. and are a relative (not absolute) path. + new_comps.push_back(L".."); } } - - // Prepend up to two leading slashes (as empty components). - new_comps.insert(new_comps.begin(), leading_slashes > 2 ? 2 : leading_slashes, wcstring()); - // Ensure e.g. './' normalizes to '.' and not empty. - if (new_comps.empty()) new_comps.push_back(L"."); - return join_strings(new_comps, sep); + wcstring result = join_strings(new_comps, sep); + // Prepend one or two leading slashes. + // Two slashes are preserved. Three+ slashes are collapsed to one. (!) + result.insert(0, leading_slashes > 2 ? 1 : leading_slashes, sep); + // Ensure ./ normalizes to . and not empty. + if (result.empty()) result.push_back(L'.'); + return result; } wcstring wdirname(const wcstring &path) {