diff --git a/SConstruct b/SConstruct index 1e37f1e73bd4..e9352e3746f1 100755 --- a/SConstruct +++ b/SConstruct @@ -531,7 +531,7 @@ for env in [test_env, client_env, env]: env[d] = os.path.join(env["prefix"], env[d]) if env["PLATFORM"] == 'win32': - env.Append(LIBS = ["wsock32", "iconv", "z"], CCFLAGS = ["-mthreads"], LINKFLAGS = ["-mthreads"], CPPDEFINES = ["_WIN32_WINNT=0x0501"]) + env.Append(LIBS = ["wsock32", "iconv", "z", "shlwapi"], CCFLAGS = ["-mthreads"], LINKFLAGS = ["-mthreads"], CPPDEFINES = ["_WIN32_WINNT=0x0501"]) if env["PLATFORM"] == 'darwin': # Mac OS X env.Append(FRAMEWORKS = "Carbon") # Carbon GUI diff --git a/projectfiles/CodeBlocks/wesnoth.cbp b/projectfiles/CodeBlocks/wesnoth.cbp index d2d85f849aa0..3498b3ba69f9 100644 --- a/projectfiles/CodeBlocks/wesnoth.cbp +++ b/projectfiles/CodeBlocks/wesnoth.cbp @@ -57,6 +57,7 @@ + diff --git a/projectfiles/CodeBlocks/wesnothd.cbp b/projectfiles/CodeBlocks/wesnothd.cbp index 4f7fc571ae7f..89389d6c0388 100644 --- a/projectfiles/CodeBlocks/wesnothd.cbp +++ b/projectfiles/CodeBlocks/wesnothd.cbp @@ -51,6 +51,7 @@ + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4dabb8716d8a..e5db451a1d53 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -62,6 +62,7 @@ if(MSVC) ${sdl-lib} ${sdlmain-lib} ws2_32.lib + shlwapi.lib ) else(MSVC) set(common-external-libs diff --git a/src/filesystem.hpp b/src/filesystem.hpp index 8808bbdfd4fa..a03535cd12e0 100644 --- a/src/filesystem.hpp +++ b/src/filesystem.hpp @@ -184,8 +184,26 @@ std::string directory_name(const std::string& file); /** * Returns the absolute path of a file. + * + * @param path Original path. + * @param normalize_separators Whether to substitute path separators with the + * platform's preferred format. + */ +std::string normalize_path(const std::string& path, + bool normalize_separators = false); + +/** + * Returns whether the path is the root of the file hierarchy. + * + * @note This function is unreliable for paths that do not exist -- it will + * always return @a false for those. */ -std::string normalize_path(const std::string &path); +bool is_root(const std::string& path); + +/** + * Returns whether the path seems to be relative. + */ +bool is_relative(const std::string& path); /** * Returns whether @a c is a path separator. @@ -195,6 +213,11 @@ std::string normalize_path(const std::string &path); */ bool is_path_sep(char c); +/** + * Returns the standard path separator for the current platform. + */ +char path_separator(); + /** * The paths manager is responsible for recording the various paths * that binary files may be located at. diff --git a/src/filesystem_boost.cpp b/src/filesystem_boost.cpp index a40c4643202f..b3bfcb21d199 100644 --- a/src/filesystem_boost.cpp +++ b/src/filesystem_boost.cpp @@ -39,6 +39,7 @@ using boost::uintmax_t; #include #include +#include #endif /* !_WIN32 */ #include "config.hpp" @@ -918,18 +919,67 @@ std::string directory_name(const std::string& file) { return path(file).parent_path().string(); } + bool is_path_sep(char c) { static const path sep = path("/").make_preferred(); const std::string s = std::string(1, c); return sep == path(s).make_preferred(); } -std::string normalize_path(const std::string &fpath) + +char path_separator() +{ + return path::preferred_separator; +} + +bool is_root(const std::string& path) +{ +#ifndef _WIN32 + error_code ec; + const bfs::path& p = bfs::canonical(path, ec); + return ec ? false : !p.has_parent_path(); +#else + // + // Boost.Filesystem is completely unreliable when it comes to detecting + // whether a path refers to a drive's root directory on Windows, so we are + // forced to take an alternative approach here. Instead of hand-parsing + // strings we'll just call a graphical shell service. + // + // There are several poorly-documented ways to refer to a drive in Windows by + // escaping the filesystem namespace using \\.\, \\?\, and \??\. We're just + // going to ignore those here, which may yield unexpected results in places + // such as the file dialog. This function really shouldn't be used for + // security validation anyway, and there are virtually infinite ways to name + // a drive's root using the NT object namespace so it's pretty pointless to + // try to catch those there. + // + // (And no, shlwapi.dll's PathIsRoot() doesn't recognize \\.\C:\, \\?\C:\, or + // \??\C:\ as roots either.) + // + // More generally, do NOT use this code in security-sensitive applications. + // + // See also: + // + const std::wstring& wpath = bfs::path{path}.make_preferred().wstring(); + return PathIsRootW(wpath.c_str()); +#endif +} + +bool is_relative(const std::string& path) +{ + return bfs::path{path}.is_relative(); +} + +std::string normalize_path(const std::string& fpath, bool normalize_separators) { - if (fpath.empty()) { + if(fpath.empty()) { return fpath; } + if(normalize_separators) { + return bfs::absolute(fpath).make_preferred().string(); + } + return bfs::absolute(fpath).string(); } diff --git a/src/tests/test_filesystem.cpp b/src/tests/test_filesystem.cpp index ee6f17801809..3a2f2b373911 100644 --- a/src/tests/test_filesystem.cpp +++ b/src/tests/test_filesystem.cpp @@ -49,6 +49,28 @@ BOOST_AUTO_TEST_CASE( test_fs_game_path_reverse_engineering ) BOOST_AUTO_TEST_CASE( test_fs_base ) { + BOOST_CHECK( is_root("/") ); + BOOST_CHECK( is_root("////") ); + BOOST_CHECK( is_root("/../") ); + BOOST_CHECK( is_root("/../.././") ); + BOOST_CHECK( is_root("/.././../.") ); + BOOST_CHECK( is_root("/.") ); + + BOOST_CHECK( is_root("/bin/..") ); + BOOST_CHECK( is_root("/bin/../bin/../.") ); + + BOOST_CHECK( !is_root("/bin") ); + BOOST_CHECK( !is_root("/bin/../bin/.") ); + + BOOST_CHECK( is_relative(".") ); + BOOST_CHECK( is_relative("..") ); + BOOST_CHECK( is_relative("../foo") ); + BOOST_CHECK( is_relative("foo") ); + BOOST_CHECK( !is_relative("/./../foo/..") ); + BOOST_CHECK( !is_relative("/foo/..") ); + BOOST_CHECK( !is_relative("/foo") ); + BOOST_CHECK( !is_relative("///foo") ); + BOOST_CHECK( is_directory("/") ); BOOST_CHECK( is_directory("/.") ); BOOST_CHECK( is_directory("/./././.") );