From e7b033d7b38bc55f934521b5f35060b43a8b0526 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 30 Nov 2016 11:47:27 +0300 Subject: Make path::normalize() to preserve ./, invalidate paths starting with \, / on Windows --- butl/buildfile | 1 + butl/path | 8 +++- butl/path.txx | 26 +++++++++---- butl/process.cxx | 28 ++++++++++---- tests/dir-iterator/buildfile | 3 +- tests/dir-iterator/testscript | 16 ++++++++ tests/path/driver.cxx | 88 +++++++++++++++++++++++-------------------- 7 files changed, 112 insertions(+), 58 deletions(-) create mode 100644 tests/dir-iterator/testscript diff --git a/butl/buildfile b/butl/buildfile index 5c5aae3..6e259c8 100644 --- a/butl/buildfile +++ b/butl/buildfile @@ -21,6 +21,7 @@ lib{butl}: \ {hxx txx }{ prefix-map } \ {hxx ixx cxx}{ process } \ {hxx cxx}{ sha256 } \ +{hxx }{ small-vector } \ {hxx txx }{ string-table } \ {hxx cxx}{ timestamp } \ {hxx cxx}{ triplet } \ diff --git a/butl/path b/butl/path index 2a32c70..2e243ff 100644 --- a/butl/path +++ b/butl/path @@ -808,7 +808,11 @@ namespace butl // Normalize the path and return *this. Normalization involves collapsing // the '.' and '..' directories if possible, collapsing multiple // directory separators, and converting all directory separators to the - // canonical form. + // canonical form. If cur_empty is true then collapse relative paths + // representing the current directory (for example, '.', './', 'foo/..') + // to an empty path. Otherwise convert it to the canonical form (./ on + // POSIX systems). Note that a non-empty path cannot become an empty one + // in the latter case. // // If actual is true, then for case-insensitive filesystems obtain the // actual spelling of the path. Only an absolute path can be actualized. @@ -818,7 +822,7 @@ namespace butl // etc.) are returned in their actual spelling. // basic_path& - normalize (bool actual = false); + normalize (bool actual = false, bool cur_empty = false); // Make the path absolute using the current directory unless it is already // absolute. Return *this. diff --git a/butl/path.txx b/butl/path.txx index 5bcaea0..0cdaf5b 100644 --- a/butl/path.txx +++ b/butl/path.txx @@ -147,7 +147,7 @@ namespace butl template basic_path& basic_path:: - normalize (bool actual) + normalize (bool actual, bool cur_empty) { if (empty ()) return *this; @@ -273,12 +273,24 @@ namespace butl p += traits::directory_separator; } - if (tsep && (!p.empty () || abs)) // Distinguish "/"-empty and "."-empty. + if (tsep) { if (p.empty ()) { - p += traits::directory_separator; - ts = -1; + // Distinguish "/"-empty and "."-empty. + // + if (abs) + { + p += traits::directory_separator; + ts = -1; + } + else if (!cur_empty) // Collapse to canonical current directory. + { + p = "."; + ts = 1; // Canonical separator is always first. + } + else // Collapse to empty path. + ts = 0; } else ts = 1; // Canonical separator is always first. @@ -313,11 +325,11 @@ namespace butl #ifdef _WIN32 // We do not support any special Windows path name notations like in C:abc, - // \\?\c:\abc, \\server\abc and \\?\UNC\server\abc (more about them at - // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx). + // /, \, /abc, \abc, \\?\c:\abc, \\server\abc and \\?\UNC\server\abc (more + // about them in "Naming Files, Paths, and Namespaces" MSDN article). // if ((n > 2 && s[1] == ':' && s[2] != '\\' && s[2] != '/') || - (n > 1 && s[0] == '\\' && s[1] == '\\')) + (n > 0 && (s[0] == '\\' || s[0] == '/'))) { if (exact) return data_type (); diff --git a/butl/process.cxx b/butl/process.cxx index c75cfbd..cf4b26d 100644 --- a/butl/process.cxx +++ b/butl/process.cxx @@ -171,7 +171,7 @@ namespace butl ep = path (move (s)); // Move back into result. if (norm) - ep.normalize (); //@@ NORM + ep.normalize (); return exists (ep.string ().c_str ()); }; @@ -212,10 +212,17 @@ namespace butl e = strchr (b, traits::path_separator); // Empty path (i.e., a double colon or a colon at the beginning or end - // of PATH) means search in the current dirrectory. + // of PATH) means search in the current dirrectory. Silently skip + // invalid paths. // - if (search (b, e != nullptr ? e - b : strlen (b))) - return r; + try + { + if (search (b, e != nullptr ? e - b : strlen (b))) + return r; + } + catch (const invalid_path&) + { + } } // If we were given a fallback, try that. @@ -575,10 +582,17 @@ namespace butl e = strchr (b, traits::path_separator); // Empty path (i.e., a double colon or a colon at the beginning or end - // of PATH) means search in the current dirrectory. + // of PATH) means search in the current dirrectory. Silently skip + // invalid paths. // - if (search (b, e != nullptr ? e - b : strlen (b))) - return r; + try + { + if (search (b, e != nullptr ? e - b : strlen (b))) + return r; + } + catch (const invalid_path&) + { + } } // Finally, if we were given a fallback, try that. This case is similar to diff --git a/tests/dir-iterator/buildfile b/tests/dir-iterator/buildfile index 5c8736e..fe310ad 100644 --- a/tests/dir-iterator/buildfile +++ b/tests/dir-iterator/buildfile @@ -2,7 +2,6 @@ # copyright : Copyright (c) 2014-2016 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file -exe{driver}: cxx{driver} ../../butl/lib{butl} -exe{driver}: test.arguments = $src_root +exe{driver}: cxx{driver} ../../butl/lib{butl} test{testscript} include ../../butl/ diff --git a/tests/dir-iterator/testscript b/tests/dir-iterator/testscript new file mode 100644 index 0000000..42dc1fa --- /dev/null +++ b/tests/dir-iterator/testscript @@ -0,0 +1,16 @@ +# file : tests/dir-iterator/testscript +# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +test.options = -v + +: file +: +mkdir a; +touch a/b; +$* a 2>"reg b" + +: dir +: +mkdir -p a/b; +$* a 2>"dir b" diff --git a/tests/path/driver.cxx b/tests/path/driver.cxx index fbcaf33..fe192ce 100644 --- a/tests/path/driver.cxx +++ b/tests/path/driver.cxx @@ -42,28 +42,24 @@ main () return x.string () == s && x.representation () == r; }; +#ifndef _WIN32 assert (test ("/", "/", "/")); assert (test ("//", "/", "/")); assert (test ("/tmp/foo", "/tmp/foo", "/tmp/foo")); assert (test ("/tmp/foo/", "/tmp/foo", "/tmp/foo/")); assert (test ("/tmp/foo//", "/tmp/foo", "/tmp/foo/")); -#ifdef _WIN32 - assert (test ("/\\", "/", "/")); + assert (dir_test ("/", "/", "/")); + assert (dir_test ("/tmp/foo/", "/tmp/foo", "/tmp/foo/")); + assert (dir_test ("tmp/foo", "tmp/foo", "tmp/foo/")); +#else assert (test ("C:", "C:", "C:")); assert (test ("C:\\", "C:", "C:\\")); assert (test ("c:/", "c:", "c:/")); assert (test ("C:\\tmp\\foo\\", "C:\\tmp\\foo", "C:\\tmp\\foo\\")); assert (test ("C:\\tmp\\foo\\/\\", "C:\\tmp\\foo", "C:\\tmp\\foo\\")); -#endif - assert (dir_test ("/", "/", "/")); - assert (dir_test ("/tmp/foo/", "/tmp/foo", "/tmp/foo/")); -#ifndef _WIN32 - assert (dir_test ("tmp/foo", "tmp/foo", "tmp/foo/")); -#else assert (dir_test ("tmp\\foo", "tmp\\foo", "tmp\\foo\\")); - assert (dir_test ("C:\\", "C:", "C:\\")); assert (dir_test ("C:\\tmp/foo\\", "C:\\tmp/foo", "C:\\tmp/foo\\")); assert (dir_test ("c:/tmp\\foo", "c:/tmp\\foo", "c:/tmp\\foo\\")); @@ -131,13 +127,15 @@ main () // base // + assert (path (".txt").base ().representation () == ".txt"); + assert (path ("foo.txt.orig").base ().representation () == "foo.txt"); + +#ifndef _WIN32 assert (path ("/").base ().representation () == "/"); assert (path ("/foo.txt").base ().representation () == "/foo"); assert (path ("/foo.txt/").base ().representation () == "/foo/"); - assert (path (".txt").base ().representation () == ".txt"); assert (path ("/.txt").base ().representation () == "/.txt"); - assert (path ("foo.txt.orig").base ().representation () == "foo.txt"); -#ifdef _WIN32 +#else assert (path ("C:").base ().representation () == "C:"); assert (path ("C:\\foo.txt").base ().representation () == "C:\\foo"); assert (path ("C:\\foo.txt\\").base ().representation () == "C:\\foo\\"); @@ -186,6 +184,7 @@ main () assert (++i != p.rend () && *i == "foo"); assert (++i == p.rend ()); } +#ifndef _WIN32 { path p ("/foo/bar"); path::iterator i (p.begin ()); @@ -202,7 +201,6 @@ main () assert (++i != p.rend () && *i == ""); assert (++i == p.rend ()); } -#ifndef _WIN32 { path p ("/"); path::iterator i (p.begin ()); @@ -244,16 +242,14 @@ main () assert (test (++p.begin (), p.end ()) == "bar"); assert (test (p.begin (), ++p.begin ()) == "foo/"); } +#ifndef _WIN32 { path p ("/foo/bar"); assert (test (p.begin (), p.end ()) == "/foo/bar"); assert (test (++p.begin (), p.end ()) == "foo/bar"); assert (test (++(++p.begin ()), p.end ()) == "bar"); -#ifndef _WIN32 assert (test (p.begin (), ++p.begin ()) == "/"); -#endif - assert (test (++p.begin (), ++(++p.begin ())) == "foo/"); assert (test (++(++p.begin ()), ++(++(++p.begin ()))) == "bar"); } @@ -263,14 +259,10 @@ main () assert (test (++p.begin (), p.end ()) == "foo/bar/"); assert (test (++(++p.begin ()), p.end ()) == "bar/"); -#ifndef _WIN32 assert (test (p.begin (), ++p.begin ()) == "/"); -#endif - assert (test (++p.begin (), ++(++p.begin ())) == "foo/"); assert (test (++(++p.begin ()), ++(++(++p.begin ()))) == "bar/"); } -#ifndef _WIN32 { path p ("/"); assert (test (p.begin (), p.end ()) == "/"); @@ -287,7 +279,6 @@ main () assert ((path ("foo/") / path ("bar/")).representation () == "foo/bar/"); assert ((path ("foo/") / path ()).representation () == "foo/"); #else - assert ((path ("\\") / path ("tmp")).representation () == "\\tmp"); assert ((path ("C:\\") / path ("tmp")).representation () == "C:\\tmp"); assert ((path ("foo\\") / path ("bar")).representation () == "foo\\bar"); assert ((path ("foo\\") / path ("bar\\")).representation () == "foo\\bar\\"); @@ -303,13 +294,16 @@ main () assert (path ("..///foo").normalize ().representation () == "../foo"); assert (path ("../../foo").normalize ().representation () == "../../foo"); assert (path (".././foo").normalize ().representation () == "../foo"); - assert (path (".").normalize ().representation () == ""); - assert (path ("././").normalize ().representation () == ""); + assert (path (".").normalize ().representation () == "./"); + assert (path (".").normalize (false, true).representation () == ""); + assert (path ("././").normalize ().representation () == "./"); + assert (path ("././").normalize (false, true).representation () == ""); assert (path ("./..").normalize ().representation () == "../"); assert (path ("./../").normalize ().representation () == "../"); assert (path ("../.").normalize ().representation () == "../"); assert (path (".././").normalize ().representation () == "../"); - assert (path ("foo/./..").normalize ().representation () == ""); + assert (path ("foo/./..").normalize ().representation () == "./"); + assert (path ("foo/./..").normalize (false, true).representation () == ""); assert (path ("/foo/./..").normalize ().representation () == "/"); assert (path ("/foo/./../").normalize ().representation () == "/"); assert (path ("./foo").normalize ().representation () == "foo"); @@ -319,27 +313,34 @@ main () assert (path ("..///foo").normalize ().representation () == "..\\foo"); assert (path ("..\\../foo").normalize ().representation () == "..\\..\\foo"); assert (path (".././foo").normalize ().representation () == "..\\foo"); - assert (path (".").normalize ().representation () == ""); - assert (path (".\\.\\").normalize ().representation () == ""); + assert (path (".").normalize ().representation () == ".\\"); + assert (path (".").normalize (false, true).representation () == ""); + assert (path (".\\.\\").normalize ().representation () == ".\\"); + assert (path (".\\.\\").normalize (false, true).representation () == ""); assert (path ("./..").normalize ().representation () == "..\\"); assert (path ("../.").normalize ().representation () == "..\\"); - assert (path ("foo/./..").normalize ().representation () == ""); + assert (path ("foo/./..").normalize ().representation () == ".\\"); + assert (path ("foo/./..").normalize (false, true).representation () == ""); assert (path ("C:/foo/./..").normalize ().representation () == "C:\\"); assert (path ("C:/foo/./../").normalize ().representation () == "C:\\"); assert (path ("./foo").normalize ().representation () == "foo"); assert (path ("./foo\\").normalize ().representation () == "foo\\"); assert (path ("C:\\").normalize ().representation () == "C:\\"); - assert (path ("C:\\Foo12//Bar").normalize ().representation () == "C:\\Foo12\\Bar"); + + assert (path ("C:\\Foo12//Bar").normalize ().representation () == + "C:\\Foo12\\Bar"); #endif // comparison // - assert (path ("/") == path ("/")); assert (path ("./foo") == path ("./foo")); assert (path ("./foo/") == path ("./foo")); assert (path ("./boo") < path ("./foo")); -#ifdef _WIN32 + +#ifndef _WIN32 + assert (path ("/") == path ("/")); +#else assert (path (".\\foo") == path ("./FoO")); assert (path (".\\foo") == path ("./foo\\")); assert (path (".\\boo") < path (".\\Foo")); @@ -371,19 +372,20 @@ main () assert (test ("foo/bar", "foo")); assert (test ("foo/bar", "foo/")); assert (!test ("foo/bar", "bar")); + +#ifndef _WIN32 assert (!test ("/foo-bar", "/foo")); assert (test ("/foo/bar", "/foo")); assert (test ("/foo/bar/baz", "/foo/bar")); assert (!test ("/foo/bar/baz", "/foo/baz")); -#ifdef _WIN32 + assert (test ("/", "/")); + assert (test ("/foo/bar/baz", "/")); +#else assert (test ("c:", "c:")); assert (test ("c:", "c:\\")); assert (!test ("c:", "d:")); assert (test ("c:\\foo", "c:")); assert (test ("c:\\foo", "c:\\")); -#else - assert (test ("/", "/")); - assert (test ("/foo/bar/baz", "/")); #endif } @@ -400,19 +402,19 @@ main () assert (test ("foo/bar", "bar")); assert (test ("foo/bar/", "bar/")); assert (!test ("foo/bar", "foo")); + +#ifndef _WIN32 + assert (test ("/", "/")); assert (!test ("/foo-bar", "bar")); assert (test ("/foo/bar", "bar")); assert (test ("/foo/bar/baz", "bar/baz")); assert (!test ("/foo/bar/baz", "bar")); - -#ifdef _WIN32 +#else assert (test ("c:", "c:")); assert (test ("c:\\", "c:")); assert (!test ("d:", "c:")); assert (test ("c:\\foo", "foo")); assert (test ("c:\\foo\\", "foo\\")); -#else - assert (test ("/", "/")); #endif } @@ -424,11 +426,14 @@ main () return path (p).leaf (path (d)).representation (); }; +#ifndef _WIN32 assert (test ("/foo", "/") == "foo"); + assert (test ("/foo/bar", "/foo/") == "bar"); +#endif + //assert (test ("foo/bar", "foo") == "bar"); assert (test ("foo/bar", "foo/") == "bar"); assert (test ("foo/bar/", "foo/") == "bar/"); - assert (test ("/foo/bar", "/foo/") == "bar"); } // directory(path) @@ -439,12 +444,15 @@ main () return path (p).directory (path (l)).representation (); }; +#ifndef _WIN32 assert (test ("/foo", "foo") == "/"); + assert (test ("/foo/bar/baz", "bar/baz") == "/foo/"); +#endif + assert (test ("foo/bar", "bar") == "foo/"); assert (test ("foo/bar/", "bar/") == "foo/"); assert (test ("foo/bar/", "bar") == "foo/"); assert (test ("foo/bar/baz", "bar/baz") == "foo/"); - assert (test ("/foo/bar/baz", "bar/baz") == "/foo/"); } // relative -- cgit v1.1