wwylele 9 лет назад
Родитель
Сommit
75ee2f8c67

+ 2 - 0
src/core/CMakeLists.txt

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

+ 98 - 0
src/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

+ 61 - 0
src/core/file_sys/path_parser.h

@@ -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 - 0
src/tests/CMakeLists.txt

@@ -1,5 +1,6 @@
 set(SRCS
             tests.cpp
+            core/file_sys/path_parser.cpp
             )
 
 set(HEADERS

+ 38 - 0
src/tests/core/file_sys/path_parser.cpp

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