FileSys: add PathParser
This commit is contained in:
parent
4dd8a831bd
commit
75ee2f8c67
|
@ -24,6 +24,7 @@ set(SRCS
|
|||
file_sys/archive_systemsavedata.cpp
|
||||
file_sys/disk_archive.cpp
|
||||
file_sys/ivfc_archive.cpp
|
||||
file_sys/path_parser.cpp
|
||||
gdbstub/gdbstub.cpp
|
||||
hle/config_mem.cpp
|
||||
hle/hle.cpp
|
||||
|
@ -168,6 +169,7 @@ set(HEADERS
|
|||
file_sys/disk_archive.h
|
||||
file_sys/file_backend.h
|
||||
file_sys/ivfc_archive.h
|
||||
file_sys/path_parser.h
|
||||
gdbstub/gdbstub.h
|
||||
hle/config_mem.h
|
||||
hle/function_wrappers.h
|
||||
|
|
|
@ -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
|
|
@ -1,5 +1,6 @@
|
|||
set(SRCS
|
||||
tests.cpp
|
||||
core/file_sys/path_parser.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
|
|
|
@ -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
|
Reference in New Issue