mirror of
https://github.com/mikage-emu/mikage-dev.git
synced 2025-01-09 15:01:00 +01:00
136 lines
5 KiB
C++
136 lines
5 KiB
C++
#include "fs_common.hpp"
|
|
#include "pxi.hpp"
|
|
|
|
#include <range/v3/algorithm/find.hpp>
|
|
#include <range/v3/algorithm/generate_n.hpp>
|
|
#include <range/v3/iterator/insert_iterators.hpp>
|
|
|
|
namespace HLE {
|
|
|
|
CommonPath CommonPath::FromUtf16(std::u16string_view utf16_data) {
|
|
while (!utf16_data.empty() && utf16_data.back() == 0) {
|
|
utf16_data.remove_suffix(1);
|
|
}
|
|
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> conversion;
|
|
return { Utf8PathType { conversion.to_bytes(utf16_data.begin(), utf16_data.end()) } };
|
|
}
|
|
|
|
CommonPath CommonPath::FromUtf16(std::basic_string_view<uint16_t> utf16_data) {
|
|
while (!utf16_data.empty() && utf16_data.back() == 0) {
|
|
utf16_data.remove_suffix(1);
|
|
}
|
|
std::vector<char16_t> char16_data(utf16_data.size());
|
|
std::copy(utf16_data.begin(), utf16_data.end(), char16_data.begin());
|
|
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> conversion;
|
|
return { Utf8PathType { conversion.to_bytes(&*char16_data.begin(), &*char16_data.end()) } };
|
|
}
|
|
|
|
CommonPath::CommonPath(OS::Thread& thread, uint32_t type, uint32_t num_bytes, const IPC::StaticBuffer& path)
|
|
: CommonPath(type, num_bytes, [&,offset=0]() mutable { return thread.ReadMemory(path.addr + offset++); }) {
|
|
}
|
|
|
|
CommonPath::CommonPath(OS::Thread& thread, uint32_t type, uint32_t num_bytes, const PXI::PXIBuffer& path)
|
|
: CommonPath(type, num_bytes, [&,offset=0]() mutable { return path.Read<uint8_t>(thread, offset++); }) {
|
|
}
|
|
|
|
template<typename ReadByte>
|
|
CommonPath::CommonPath(uint32_t type, uint32_t num_bytes, ReadByte&& read) {
|
|
switch (type) {
|
|
case 1: // empty path
|
|
{
|
|
data = Utf8PathType { };
|
|
break;
|
|
}
|
|
|
|
case 2: // binary path
|
|
{
|
|
data = BinaryPathType { };
|
|
auto& path_data = std::get<BinaryPathType>(data);
|
|
|
|
if (num_bytes > max_length) {
|
|
throw std::runtime_error(fmt::format("Path length {} exceeded maximum {}", num_bytes, path_data.capacity()));
|
|
}
|
|
|
|
ranges::generate_n(ranges::back_inserter(path_data), num_bytes, read);
|
|
break;
|
|
}
|
|
|
|
case 3: // ASCII path
|
|
{
|
|
data = Utf8PathType { };
|
|
auto& path = std::get<Utf8PathType>(data);
|
|
|
|
path.reserve(num_bytes);
|
|
ranges::generate_n(ranges::back_inserter(path), num_bytes, read);
|
|
|
|
// Strip characters past the null terminator
|
|
path.erase(ranges::find(path, '\0'), path.end());
|
|
break;
|
|
}
|
|
|
|
case 4: // UTF-16 path
|
|
{
|
|
if (num_bytes % 2)
|
|
throw std::runtime_error("Expected path size for UTF16 to be a multiple of 2");
|
|
|
|
// Read in data as UTF-16 with host byte order
|
|
std::u16string utf16_data;
|
|
utf16_data.reserve(num_bytes / 2);
|
|
auto ReadFromAddr = [&]() -> char16_t {
|
|
uint16_t low = read();
|
|
uint32_t high = read();
|
|
return low | (high << uint16_t { 8 });
|
|
};
|
|
ranges::generate_n(ranges::back_inserter(utf16_data), num_bytes / 2, ReadFromAddr);
|
|
|
|
// Strip characters past the null terminator
|
|
utf16_data.erase(ranges::find(utf16_data, u'\0'), utf16_data.end());
|
|
|
|
*this = FromUtf16(utf16_data);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw std::runtime_error(fmt::format("Unknown path type {}", type));
|
|
}
|
|
}
|
|
|
|
ValidatedHostPath PathValidator::ValidateAndGetSandboxedTreePath(const Utf8PathType& utf8_path) const {
|
|
auto path = utf8_path.to_std_path().lexically_normal();
|
|
auto path_elements = std::distance(path.begin(), path.end());
|
|
if (path_elements == 0 || *path.begin() != "/") {
|
|
test_mode ? throw IPC::IPCError { 0, 0xe0e046be } // TODO: Throw PathValidator errors instead
|
|
: throw std::runtime_error("Path does not start with '/'");
|
|
}
|
|
|
|
if (path_elements > 1 && *std::next(path.begin()) == "..") {
|
|
test_mode ? throw IPC::IPCError { 0, 0xe0e046be }
|
|
: throw std::runtime_error("Path escapes root directory");
|
|
}
|
|
|
|
// const uint32_t max_filename_length = 0x10; // TODO: Where does this come from? Seems overly short for HostArchives
|
|
const uint32_t max_filename_length = 0x100;
|
|
if (std::prev(path.end())->u8string().size() > max_filename_length) {
|
|
test_mode ? throw IPC::IPCError { 0, 0xe0e046c7 }
|
|
: throw std::runtime_error("Filename too long");
|
|
}
|
|
|
|
// Append path to the sandbox root and double-check that we won't leak out of it
|
|
auto sandbox_root = GetSandboxHostRoot();
|
|
if (!sandbox_root.is_absolute()) {
|
|
// TODO: Required for cfg
|
|
// throw std::runtime_error("Sandbox root is not an absolute path: " + sandbox_root.string());
|
|
}
|
|
sandbox_root = canonical(sandbox_root);
|
|
|
|
auto new_path = sandbox_root;
|
|
new_path += utf8_path;
|
|
new_path = new_path.lexically_normal();
|
|
if (new_path.begin() != std::search(new_path.begin(), new_path.end(), sandbox_root.begin(), sandbox_root.end())) {
|
|
throw std::runtime_error(fmt::format("Requested path leaked out of sandbox: Got path {}, expected a child of {}", new_path, sandbox_root));
|
|
}
|
|
return ValidatedHostPath { std::move(new_path) };
|
|
}
|
|
|
|
}
|