5 changed files with 200 additions and 0 deletions
-
2src/core/CMakeLists.txt
-
98src/core/file_sys/path_parser.cpp
-
61src/core/file_sys/path_parser.h
-
1src/tests/CMakeLists.txt
-
38src/tests/core/file_sys/path_parser.cpp
@ -0,0 +1,98 @@ |
|||
// Copyright 2016 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <algorithm>
|
|||
#include <set>
|
|||
#include "common/file_util.h"
|
|||
#include "common/string_util.h"
|
|||
#include "core/file_sys/path_parser.h"
|
|||
|
|||
namespace FileSys { |
|||
|
|||
PathParser::PathParser(const Path& path) { |
|||
if (path.GetType() != LowPathType::Char && path.GetType() != LowPathType::Wchar) { |
|||
is_valid = false; |
|||
return; |
|||
} |
|||
|
|||
auto path_string = path.AsString(); |
|||
if (path_string.size() == 0 || path_string[0] != '/') { |
|||
is_valid = false; |
|||
return; |
|||
} |
|||
|
|||
// Filter out invalid characters for the host system.
|
|||
// Although some of these characters are valid on 3DS, they are unlikely to be used by games.
|
|||
if (std::find_if(path_string.begin(), path_string.end(), [](char c) { |
|||
static const std::set<char> invalid_chars{'<', '>', '\\', '|', ':', '\"', '*', '?'}; |
|||
return invalid_chars.find(c) != invalid_chars.end(); |
|||
}) != path_string.end()) { |
|||
is_valid = false; |
|||
return; |
|||
} |
|||
|
|||
Common::SplitString(path_string, '/', path_sequence); |
|||
|
|||
auto begin = path_sequence.begin(); |
|||
auto end = path_sequence.end(); |
|||
end = std::remove_if(begin, end, [](std::string& str) { return str == "" || str == "."; }); |
|||
path_sequence = std::vector<std::string>(begin, end); |
|||
|
|||
// checks if the path is out of bounds.
|
|||
int level = 0; |
|||
for (auto& node : path_sequence) { |
|||
if (node == "..") { |
|||
--level; |
|||
if (level < 0) { |
|||
is_valid = false; |
|||
return; |
|||
} |
|||
} else { |
|||
++level; |
|||
} |
|||
} |
|||
|
|||
is_valid = true; |
|||
is_root = level == 0; |
|||
} |
|||
|
|||
PathParser::HostStatus PathParser::GetHostStatus(const std::string& mount_point) const { |
|||
auto path = mount_point; |
|||
if (!FileUtil::IsDirectory(path)) |
|||
return InvalidMountPoint; |
|||
if (path_sequence.empty()) { |
|||
return DirectoryFound; |
|||
} |
|||
|
|||
for (auto iter = path_sequence.begin(); iter != path_sequence.end() - 1; iter++) { |
|||
if (path.back() != '/') |
|||
path += '/'; |
|||
path += *iter; |
|||
|
|||
if (!FileUtil::Exists(path)) |
|||
return PathNotFound; |
|||
if (FileUtil::IsDirectory(path)) |
|||
continue; |
|||
return FileInPath; |
|||
} |
|||
|
|||
path += "/" + path_sequence.back(); |
|||
if (!FileUtil::Exists(path)) |
|||
return NotFound; |
|||
if (FileUtil::IsDirectory(path)) |
|||
return DirectoryFound; |
|||
return FileFound; |
|||
} |
|||
|
|||
std::string PathParser::BuildHostPath(const std::string& mount_point) const { |
|||
std::string path = mount_point; |
|||
for (auto& node : path_sequence) { |
|||
if (path.back() != '/') |
|||
path += '/'; |
|||
path += node; |
|||
} |
|||
return path; |
|||
} |
|||
|
|||
} // namespace FileSys
|
|||
@ -0,0 +1,61 @@ |
|||
// Copyright 2016 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <string> |
|||
#include <vector> |
|||
#include "core/file_sys/archive_backend.h" |
|||
|
|||
namespace FileSys { |
|||
|
|||
/** |
|||
* A helper class parsing and verifying a string-type Path. |
|||
* Every archives with a sub file system should use this class to parse the path argument and check |
|||
* the status of the file / directory in question on the host file system. |
|||
*/ |
|||
class PathParser { |
|||
public: |
|||
PathParser(const Path& path); |
|||
|
|||
/** |
|||
* Checks if the Path is valid. |
|||
* This function should be called once a PathParser is constructed. |
|||
* A Path is valid if: |
|||
* - it is a string path (with type LowPathType::Char or LowPathType::Wchar), |
|||
* - it starts with "/" (this seems a hard requirement in real 3DS), |
|||
* - it doesn't contain invalid characters, and |
|||
* - it doesn't go out of the root directory using "..". |
|||
*/ |
|||
bool IsValid() const { |
|||
return is_valid; |
|||
} |
|||
|
|||
/// Checks if the Path represents the root directory. |
|||
bool IsRootDirectory() const { |
|||
return is_root; |
|||
} |
|||
|
|||
enum HostStatus { |
|||
InvalidMountPoint, |
|||
PathNotFound, // "/a/b/c" when "a" doesn't exist |
|||
FileInPath, // "/a/b/c" when "a" is a file |
|||
FileFound, // "/a/b/c" when "c" is a file |
|||
DirectoryFound, // "/a/b/c" when "c" is a directory |
|||
NotFound // "/a/b/c" when "a/b/" exists but "c" doesn't exist |
|||
}; |
|||
|
|||
/// Checks the status of the specified file / directory by the Path on the host file system. |
|||
HostStatus GetHostStatus(const std::string& mount_point) const; |
|||
|
|||
/// Builds a full path on the host file system. |
|||
std::string BuildHostPath(const std::string& mount_point) const; |
|||
|
|||
private: |
|||
std::vector<std::string> path_sequence; |
|||
bool is_valid{}; |
|||
bool is_root{}; |
|||
}; |
|||
|
|||
} // namespace FileSys |
|||
@ -0,0 +1,38 @@ |
|||
// Copyright 2016 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <catch.hpp>
|
|||
#include "common/file_util.h"
|
|||
#include "core/file_sys/path_parser.h"
|
|||
|
|||
namespace FileSys { |
|||
|
|||
TEST_CASE("PathParser", "[core][file_sys]") { |
|||
REQUIRE(!PathParser(Path(std::vector<u8>{})).IsValid()); |
|||
REQUIRE(!PathParser(Path("a")).IsValid()); |
|||
REQUIRE(!PathParser(Path("/|")).IsValid()); |
|||
REQUIRE(PathParser(Path("/a")).IsValid()); |
|||
REQUIRE(!PathParser(Path("/a/b/../../c/../../d")).IsValid()); |
|||
REQUIRE(PathParser(Path("/a/b/../c/../../d")).IsValid()); |
|||
REQUIRE(PathParser(Path("/")).IsRootDirectory()); |
|||
REQUIRE(!PathParser(Path("/a")).IsRootDirectory()); |
|||
REQUIRE(PathParser(Path("/a/..")).IsRootDirectory()); |
|||
} |
|||
|
|||
TEST_CASE("PathParser - Host file system", "[core][file_sys]") { |
|||
std::string test_dir = "./test"; |
|||
FileUtil::CreateDir(test_dir); |
|||
FileUtil::CreateDir(test_dir + "/z"); |
|||
FileUtil::CreateEmptyFile(test_dir + "/a"); |
|||
|
|||
REQUIRE(PathParser(Path("/a")).GetHostStatus(test_dir) == PathParser::FileFound); |
|||
REQUIRE(PathParser(Path("/b")).GetHostStatus(test_dir) == PathParser::NotFound); |
|||
REQUIRE(PathParser(Path("/z")).GetHostStatus(test_dir) == PathParser::DirectoryFound); |
|||
REQUIRE(PathParser(Path("/a/c")).GetHostStatus(test_dir) == PathParser::FileInPath); |
|||
REQUIRE(PathParser(Path("/b/c")).GetHostStatus(test_dir) == PathParser::PathNotFound); |
|||
|
|||
FileUtil::DeleteDirRecursively(test_dir); |
|||
} |
|||
|
|||
} // namespace FileSys
|
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue