Skip to content

Commit bd69dea

Browse files
committed
Fix base::get_absolute_path() fixing base::normalize_path() function
base::get_absolute_path() resulted in bugs when more than two or more double dots were present in the input path. The source of the error was the base::normalize_path() function which has been fixed. Also added tests in 'fs_tests.cpp'. This fix is part of the solution of aseprite/aseprite#4851.
1 parent c84b89b commit bd69dea

File tree

2 files changed

+42
-48
lines changed

2 files changed

+42
-48
lines changed

base/fs.cpp

+35-48
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,36 @@ std::string normalize_path(const std::string& _path)
281281
// Replace multiple slashes with a single path_separator.
282282
std::string path = fix_path_separators(_path);
283283

284-
std::string fn;
285-
fn.reserve(path.size());
284+
std::vector<std::string> parts;
285+
split_string(path, parts, path_separators);
286286

287-
// Add the first separator for absolute paths.
288-
if (!path.empty() && path[0] == path_separator) {
289-
fn.push_back(path_separator);
287+
std::vector<std::string> fn_parts;
288+
bool has_root = (!path.empty() && path[0] == path_separator);
289+
bool last_dot = (parts.back() == ".");
290+
bool last_slash = parts.back().empty();
291+
if (last_slash || last_dot)
292+
parts.pop_back();
293+
294+
for (const auto& part : parts) {
295+
// Skip each dot part.
296+
if (part.empty() || part == ".")
297+
continue;
298+
else if (part == "..") {
299+
if (has_root && fn_parts.empty())
300+
continue;
301+
if (!fn_parts.empty() && fn_parts.back() != "..")
302+
fn_parts.pop_back();
303+
else
304+
fn_parts.push_back(part);
305+
}
306+
else
307+
fn_parts.push_back(part);
308+
}
290309

310+
// Reconstruct the filename 'fn' from 'fn_parts'
311+
std::string fn;
312+
if (has_root) {
313+
fn.push_back(path_separator);
291314
#if LAF_WINDOWS
292315
// Add the second separator for network paths.
293316
if (path.size() >= 2 && path[1] == path_separator) {
@@ -296,51 +319,15 @@ std::string normalize_path(const std::string& _path)
296319
#endif
297320
}
298321

299-
std::vector<std::string> parts;
300-
split_string(path, parts, path_separators);
301-
302-
// Last element generates a final dot or slash in normalized path.
303-
bool last_dot = false;
322+
for (const auto& part : fn_parts)
323+
fn = join_path(fn, part);
304324

305-
auto n = int(parts.size());
306-
for (int i = 0; i < n; ++i) {
307-
const auto& part = parts[i];
308-
309-
// Remove each dot part.
310-
if (part == ".") {
311-
last_dot = true;
312-
313-
if (i + 1 == n)
314-
break;
315-
316-
fn = join_path(fn, std::string());
317-
continue;
318-
}
319-
320-
if (!part.empty())
321-
last_dot = false;
325+
if (!fn.empty() &&
326+
parts.back() != ".." &&
327+
(last_slash || last_dot))
328+
fn.push_back(path_separator);
322329

323-
if (part != ".." && i + 1 < n && parts[i + 1] == "..") {
324-
// Skip this "part/.."
325-
++i;
326-
last_dot = true;
327-
}
328-
else if (!part.empty()) {
329-
fn = join_path(fn, part);
330-
}
331-
else
332-
last_dot = true;
333-
}
334-
if (last_dot) {
335-
if (fn.empty())
336-
fn = ".";
337-
else if (fn.back() != path_separator &&
338-
// Don't include trailing slash for ".." filename
339-
get_file_name(fn) != "..") {
340-
fn.push_back(path_separator);
341-
}
342-
}
343-
return fn;
330+
return (fn.empty() ? "." : fn);
344331
}
345332

346333
bool has_file_extension(const std::string& filename, const base::paths& extensions)

base/fs_tests.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,14 @@ TEST(FS, GetAbsolutePath)
245245

246246
#if LAF_WINDOWS
247247
EXPECT_EQ("C:\\file", get_absolute_path("C:/path/../file"));
248+
EXPECT_EQ("C:\\user\\file", get_absolute_path("C:/user/name/path/../../file"));
249+
EXPECT_EQ("C:\\file", get_absolute_path("C:/user/name/path/../../../file"));
250+
EXPECT_EQ("C:\\path\\file", get_absolute_path("C:/user/name/..//.././path/tag/../file"));
248251
#else
249252
EXPECT_EQ("/file", get_absolute_path("/path/../file"));
253+
EXPECT_EQ("/user/file", get_absolute_path("/user/name/path/../../file"));
254+
EXPECT_EQ("/file", get_absolute_path("/user/name/path/../../../file"));
255+
EXPECT_EQ("/path/file", get_absolute_path("/user/name/..//.././path/tag/../file"));
250256
#endif
251257
}
252258

@@ -288,6 +294,7 @@ TEST(FS, NormalizePath)
288294
EXPECT_EQ(".", normalize_path("a/.."));
289295
EXPECT_EQ("..", normalize_path("../a/.."));
290296
EXPECT_EQ(".." + sep + "..", normalize_path("../a/../.."));
297+
EXPECT_EQ(".." + sep + "..", normalize_path("../a/../../"));
291298
EXPECT_EQ("..", normalize_path("a/../.."));
292299
EXPECT_EQ(sep + "b", normalize_path("/a/../b"));
293300

0 commit comments

Comments
 (0)