fs: Add utility functions for the new file dialog

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.
This commit is contained in:
Ignacio R. Morelle 2016-09-14 07:03:20 -03:00
parent e38dd8ff16
commit ea9d077b89
7 changed files with 102 additions and 4 deletions

View file

@ -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

View file

@ -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" />

View file

@ -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" />

View file

@ -62,6 +62,7 @@ if(MSVC)
${sdl-lib}
${sdlmain-lib}
ws2_32.lib
shlwapi.lib
)
else(MSVC)
set(common-external-libs

View file

@ -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);
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.
*/
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.

View file

@ -39,6 +39,7 @@ using boost::uintmax_t;
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#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()
{
if (fpath.empty()) {
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()) {
return fpath;
}
if(normalize_separators) {
return bfs::absolute(fpath).make_preferred().string();
}
return bfs::absolute(fpath).string();
}

View file

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