Skip to content

Commit

Permalink
fs: Add utility functions for the new file dialog
Browse files Browse the repository at this point in the history
These include an alternate mode of normalize_path() that enforces the
platform's preferred path delimiter (i.e. backslash on Windows) on the
output, and a function to detect whether a path refers to a root
directory.

Unfortunately, the last bit requires introducing a new link-time
dependency on Windows, against a system library. It's guaranteed to be
always there but it seems kind of a waste. The alternative would be to
hand-parse the string but that seems even more of a waste. And no,
Boost.Filesystem can't do this in a straightforward fashion right now.
  • Loading branch information
irydacea committed Oct 5, 2016
1 parent e38dd8f commit ea9d077
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 4 deletions.
2 changes: 1 addition & 1 deletion SConstruct
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions projectfiles/CodeBlocks/wesnoth.cbp
Expand Up @@ -57,6 +57,7 @@
<Add library="SDL2_ttf" />
<Add library="freetype" />
<Add library="ws2_32" />
<Add library="shlwapi" />
<Add library="pango-1.0" />
<Add library="pangocairo-1.0" />
<Add library="cairo" />
Expand Down
1 change: 1 addition & 0 deletions projectfiles/CodeBlocks/wesnothd.cbp
Expand Up @@ -51,6 +51,7 @@
<Add library="SDL2" />
<Add library="ws2_32" />
<Add library="wsock32" />
<Add library="shlwapi" />
<Add library="libboost_iostreams-mgw51-mt-1_61.a" />
<Add library="libboost_bzip2-mgw51-mt-1_61.a" />
<Add library="libboost_filesystem-mgw51-mt-1_61.a" />
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Expand Up @@ -62,6 +62,7 @@ if(MSVC)
${sdl-lib}
${sdlmain-lib}
ws2_32.lib
shlwapi.lib
)
else(MSVC)
set(common-external-libs
Expand Down
25 changes: 24 additions & 1 deletion src/filesystem.hpp
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
54 changes: 52 additions & 2 deletions src/filesystem_boost.cpp
Expand Up @@ -39,6 +39,7 @@ using boost::uintmax_t;

#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#endif /* !_WIN32 */

#include "config.hpp"
Expand Down Expand Up @@ -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: <https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html>
//
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();
}

Expand Down
22 changes: 22 additions & 0 deletions src/tests/test_filesystem.cpp
Expand Up @@ -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("/./././.") );
Expand Down

0 comments on commit ea9d077

Please sign in to comment.