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("/./././.") );