diff --git a/libraries/libstratosphere/include/stratosphere/fs.hpp b/libraries/libstratosphere/include/stratosphere/fs.hpp index 413d23b28..c6b5bc537 100644 --- a/libraries/libstratosphere/include/stratosphere/fs.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs.hpp @@ -40,7 +40,6 @@ #include #include #include -#include #include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_path_tool.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_path_tool.hpp deleted file mode 100644 index 9d9e145b8..000000000 --- a/libraries/libstratosphere/include/stratosphere/fs/fs_path_tool.hpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2018-2020 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once -#include -#include - -namespace ams::fs { - - namespace StringTraits { - - constexpr inline char DirectorySeparator = '/'; - constexpr inline char DriveSeparator = ':'; - constexpr inline char Dot = '.'; - constexpr inline char NullTerminator = '\x00'; - - constexpr inline char AlternateDirectorySeparator = '\\'; - } - - class PathTool { - public: - static constexpr const char RootPath[] = "/"; - public: - static constexpr inline bool IsAlternateSeparator(char c) { - return c == StringTraits::AlternateDirectorySeparator; - } - - static constexpr inline bool IsSeparator(char c) { - return c == StringTraits::DirectorySeparator; - } - - static constexpr inline bool IsAnySeparator(char c) { - return IsSeparator(c) || IsAlternateSeparator(c); - } - - static constexpr inline bool IsNullTerminator(char c) { - return c == StringTraits::NullTerminator; - } - - static constexpr inline bool IsDot(char c) { - return c == StringTraits::Dot; - } - - static constexpr inline bool IsWindowsDriveCharacter(char c) { - return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); - } - - static constexpr inline bool IsDriveSeparator(char c) { - return c == StringTraits::DriveSeparator; - } - - static constexpr inline bool IsWindowsAbsolutePath(const char *p) { - return IsWindowsDriveCharacter(p[0]) && IsDriveSeparator(p[1]); - } - - static constexpr inline bool IsUnc(const char *p) { - return (IsSeparator(p[0]) && IsSeparator(p[1])) || (IsAlternateSeparator(p[0]) && IsAlternateSeparator(p[1])); - } - - static constexpr inline bool IsCurrentDirectory(const char *p) { - return IsDot(p[0]) && (IsSeparator(p[1]) || IsNullTerminator(p[1])); - } - - static constexpr inline bool IsParentDirectory(const char *p) { - return IsDot(p[0]) && IsDot(p[1]) && (IsSeparator(p[2]) || IsNullTerminator(p[2])); - } - - static Result Normalize(char *out, size_t *out_len, const char *src, size_t max_out_size, bool unc_preserved = false); - static Result IsNormalized(bool *out, const char *path); - - static bool IsSubPath(const char *lhs, const char *rhs); - }; - -} diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_path_utils.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_path_utils.hpp index 59174ce20..7ddeb8a78 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fs_path_utils.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_path_utils.hpp @@ -19,15 +19,59 @@ namespace ams::fs { + namespace StringTraits { + + constexpr inline char DirectorySeparator = '/'; + constexpr inline char DriveSeparator = ':'; + constexpr inline char Dot = '.'; + constexpr inline char NullTerminator = '\x00'; + + constexpr inline char AlternateDirectorySeparator = '\\'; + + } + + /* Windows path utilities. */ + constexpr inline bool IsWindowsDrive(const char *path) { + AMS_ASSERT(path != nullptr); + + const char c = path[0]; + return (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')) && path[1] == StringTraits::DriveSeparator; + } + + constexpr inline bool IsUnc(const char *path) { + return (path[0] == StringTraits::DirectorySeparator && path[1] == StringTraits::DirectorySeparator) || + (path[0] == StringTraits::AlternateDirectorySeparator && path[1] == StringTraits::AlternateDirectorySeparator); + } + + constexpr inline s64 GetWindowsPathSkipLength(const char *path) { + if (IsWindowsDrive(path)) { + return 2; + } + if (IsUnc(path)) { + for (s64 i = 2; path[i] != StringTraits::NullTerminator; ++i) { + if (path[i] == '$' || path[i] == ':') { + return i + 1; + } + } + } + return 0; + } + + /* Path utilities. */ inline void Replace(char *dst, size_t dst_size, char old_char, char new_char) { - for (char *cur = dst; cur < dst + dst_size && *cur != '\x00'; cur++) { + AMS_ASSERT(dst != nullptr); + for (char *cur = dst; cur < dst + dst_size && *cur != StringTraits::NullTerminator; ++cur) { if (*cur == old_char) { *cur = new_char; } } } + Result FspPathPrintf(fssrv::sf::FspPath *dst, const char *format, ...) __attribute__((format(printf, 2, 3))); + inline Result FspPathPrintf(fssrv::sf::FspPath *dst, const char *format, ...) { + AMS_ASSERT(dst != nullptr); + /* Format the path. */ std::va_list va_list; va_start(va_list, format); @@ -43,6 +87,37 @@ namespace ams::fs { return ResultSuccess(); } - Result VerifyPath(const char *path, size_t max_path_len, size_t max_name_len); + Result VerifyPath(const char *path, int max_path_len, int max_name_len); + + bool IsSubPath(const char *lhs, const char *rhs); + + /* Path normalization. */ + class PathNormalizer { + public: + static constexpr const char RootPath[] = "/"; + public: + static constexpr inline bool IsSeparator(char c) { + return c == StringTraits::DirectorySeparator; + } + + static constexpr inline bool IsAnySeparator(char c) { + return c == StringTraits::DirectorySeparator || c == StringTraits::AlternateDirectorySeparator; + } + + static constexpr inline bool IsNullTerminator(char c) { + return c == StringTraits::NullTerminator; + } + + static constexpr inline bool IsCurrentDirectory(const char *p) { + return p[0] == StringTraits::Dot && (IsSeparator(p[1]) || IsNullTerminator(p[1])); + } + + static constexpr inline bool IsParentDirectory(const char *p) { + return p[0] == StringTraits::Dot && p[1] == StringTraits::Dot && (IsSeparator(p[2]) || IsNullTerminator(p[2])); + } + + static Result Normalize(char *out, size_t *out_len, const char *src, size_t max_out_size, bool unc_preserved = false, bool has_mount_name = false); + static Result IsNormalized(bool *out, const char *path, bool unc_preserved = false, bool has_mount_name = false); + }; } diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_remote_filesystem.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_remote_filesystem.hpp index 0d32894ea..b1b95c95c 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fs_remote_filesystem.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_remote_filesystem.hpp @@ -20,7 +20,6 @@ #include #include #include -#include #include namespace ams::fs { @@ -100,12 +99,12 @@ namespace ams::fs { out_path->str[sizeof(out_path->str) - 1] = '\x00'; /* Replace directory separators. */ - Replace(out_path->str, sizeof(out_path->str) - 1, StringTraits::AlternateDirectorySeparator, StringTraits::DirectorySeparator); + fs::Replace(out_path->str, sizeof(out_path->str) - 1, StringTraits::AlternateDirectorySeparator, StringTraits::DirectorySeparator); /* Get lengths. */ - const auto mount_name_len = PathTool::IsWindowsAbsolutePath(path) ? 2 : 0; - const auto rel_path = out_path->str + mount_name_len; - const auto max_len = fs::EntryNameLengthMax - mount_name_len; + const auto skip_len = fs::GetWindowsPathSkipLength(path); + const auto rel_path = out_path->str + skip_len; + const auto max_len = fs::EntryNameLengthMax - skip_len; return VerifyPath(rel_path, max_len, max_len); } public: diff --git a/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_path_normalizer.hpp b/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_path_normalizer.hpp index 6bace6065..2d970b4ec 100644 --- a/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_path_normalizer.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_path_normalizer.hpp @@ -31,7 +31,7 @@ namespace ams::fssrv { Option_AcceptEmpty = BIT(4), }; private: - using Buffer = std::unique_ptr; + using Buffer = std::unique_ptr; private: Buffer buffer; const char *path; @@ -39,8 +39,9 @@ namespace ams::fssrv { private: static Result Normalize(const char **out_path, Buffer *out_buf, const char *path, bool preserve_unc, bool preserve_tail_sep, bool has_mount_name); public: + /* TODO: Remove non-option constructor. */ explicit PathNormalizer(const char *p) : buffer(), path(nullptr), result(ResultSuccess()) { - this->result = Normalize(&this->path, &this->buffer, p, false, false, false); + this->result = Normalize(std::addressof(this->path), std::addressof(this->buffer), p, false, false, false); } PathNormalizer(const char *p, u32 option) : buffer(), path(nullptr), result(ResultSuccess()) { @@ -50,15 +51,15 @@ namespace ams::fssrv { const bool preserve_unc = (option & Option_PreserveUnc); const bool preserve_tail_sep = (option & Option_PreserveTailSeparator); const bool has_mount_name = (option & Option_HasMountName); - this->result = Normalize(&this->path, &this->buffer, p, preserve_unc, preserve_tail_sep, has_mount_name); + this->result = Normalize(std::addressof(this->path), std::addressof(this->buffer), p, preserve_unc, preserve_tail_sep, has_mount_name); } } - inline Result GetResult() const { + Result GetResult() const { return this->result; } - inline const char * GetPath() const { + const char *GetPath() const { return this->path; } }; diff --git a/libraries/libstratosphere/include/stratosphere/fssystem.hpp b/libraries/libstratosphere/include/stratosphere/fssystem.hpp index 2be11f318..d95feee40 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem.hpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_utility.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_utility.hpp index fefa2df3c..4e6b77a8b 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_utility.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_utility.hpp @@ -14,11 +14,11 @@ * along with this program. If not, see . */ #pragma once -#include "../fs/fs_common.hpp" -#include "../fs/fs_file.hpp" -#include "../fs/fs_directory.hpp" -#include "../fs/fs_filesystem.hpp" -#include "fssystem_path_tool.hpp" +#include +#include +#include +#include +#include namespace ams::fssystem { @@ -70,7 +70,7 @@ namespace ams::fssystem { } /* Restore parent path. */ - work_path[parent_len] = StringTraits::NullTerminator; + work_path[parent_len] = fs::StringTraits::NullTerminator; } return ResultSuccess(); @@ -91,13 +91,13 @@ namespace ams::fssystem { /* Copy root path in, add a / if necessary. */ std::memcpy(work_path, root_path, root_path_len); - if (!PathTool::IsSeparator(work_path[root_path_len - 1])) { - work_path[root_path_len++] = StringTraits::DirectorySeparator; + if (!fs::PathNormalizer::IsSeparator(work_path[root_path_len - 1])) { + work_path[root_path_len++] = fs::StringTraits::DirectorySeparator; } /* Make sure the result path is still valid. */ R_UNLESS(root_path_len <= fs::EntryNameLengthMax, fs::ResultTooLongPath()); - work_path[root_path_len] = StringTraits::NullTerminator; + work_path[root_path_len] = fs::StringTraits::NullTerminator; return impl::IterateDirectoryRecursivelyImpl(fs, work_path, work_path_size, dir_ent_buf, on_enter_dir, on_exit_dir, on_file); } @@ -111,7 +111,7 @@ namespace ams::fssystem { template Result IterateDirectoryRecursively(fs::fsa::IFileSystem *fs, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) { - return IterateDirectoryRecursively(fs, PathTool::RootPath, on_enter_dir, on_exit_dir, on_file); + return IterateDirectoryRecursively(fs, fs::PathNormalizer::RootPath, on_enter_dir, on_exit_dir, on_file); } /* TODO: Cleanup API */ diff --git a/libraries/libstratosphere/source/fs/fs_bis.cpp b/libraries/libstratosphere/source/fs/fs_bis.cpp index a90ee12b8..0d9694cd9 100644 --- a/libraries/libstratosphere/source/fs/fs_bis.cpp +++ b/libraries/libstratosphere/source/fs/fs_bis.cpp @@ -72,7 +72,7 @@ namespace ams::fs { fssrv::sf::Path sf_path; if (len > 0) { - const bool ending_sep = PathTool::IsSeparator(root_path[len - 1]); + const bool ending_sep = PathNormalizer::IsSeparator(root_path[len - 1]); FspPathPrintf(std::addressof(sf_path), "%s%s", root_path, ending_sep ? "" : "/"); } else { sf_path.str[0] = '\x00'; diff --git a/libraries/libstratosphere/source/fs/fs_path_normalizer.cpp b/libraries/libstratosphere/source/fs/fs_path_normalizer.cpp new file mode 100644 index 000000000..7f1beadba --- /dev/null +++ b/libraries/libstratosphere/source/fs/fs_path_normalizer.cpp @@ -0,0 +1,569 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fs { + + namespace { + + Result CheckSharedName(const char *path, int len) { + if (len == 1) { + R_UNLESS(path[0] != StringTraits::Dot, fs::ResultInvalidPathFormat()); + } else if (len == 2) { + R_UNLESS(path[0] != StringTraits::Dot || path[1] != StringTraits::Dot, fs::ResultInvalidPathFormat()); + } + + return ResultSuccess(); + } + + Result ParseWindowsPath(const char **out_path, char *out, size_t *out_windows_path_len, bool *out_normalized, const char *path, size_t max_out_size, bool has_mount_name) { + /* Prepare to parse. */ + const char * const path_start = path; + if (out_normalized != nullptr) { + *out_normalized = true; + } + + /* Handle start of path. */ + bool skipped_mount = false; + auto prefix_len = 0; + if (has_mount_name) { + if (PathNormalizer::IsSeparator(path[0]) && path[1] == StringTraits::AlternateDirectorySeparator && path[2] == StringTraits::AlternateDirectorySeparator) { + path += 1; + prefix_len = 1; + } else { + /* Advance past separators. */ + while (PathNormalizer::IsSeparator(path[0])) { + ++path; + } + if (path != path_start) { + if (path - path_start == 1 || IsWindowsDrive(path)) { + prefix_len = 1; + } else { + if (path - path_start > 2 && out_normalized != nullptr) { + *out_normalized = false; + return ResultSuccess(); + } + path -= 2; + skipped_mount = true; + } + } + } + } else if (PathNormalizer::IsSeparator(path[0]) && !IsUnc(path)) { + path += 1; + prefix_len = 1; + } + + + /* Parse the path. */ + const char *trimmed_path = path_start; + if (IsWindowsDrive(path)) { + /* Find the first separator. */ + int i; + for (i = 2; !PathNormalizer::IsNullTerminator(path[i]); ++i) { + if (PathNormalizer::IsAnySeparator(path[i])) { + break; + } + } + trimmed_path = path + i; + + const size_t win_path_len = trimmed_path - path_start; + if (out != nullptr) { + R_UNLESS(win_path_len <= max_out_size, fs::ResultTooLongPath()); + std::memcpy(out, path_start, win_path_len); + } + *out_path = trimmed_path; + *out_windows_path_len = win_path_len; + } else if (IsUnc(path)) { + if (PathNormalizer::IsAnySeparator(path[2])) { + AMS_ASSERT(!has_mount_name); + return fs::ResultInvalidPathFormat(); + } + + int cur_part_ofs = 0; + bool needs_sep_fix = false; + for (auto i = 2; !PathNormalizer::IsNullTerminator(path[i]); ++i) { + if (cur_part_ofs == 0 && path[i] == StringTraits::AlternateDirectorySeparator) { + needs_sep_fix = true; + if (out_normalized != nullptr) { + *out_normalized = false; + return ResultSuccess(); + } + } + if (PathNormalizer::IsAnySeparator(path[i])) { + if (path[i] == StringTraits::AlternateDirectorySeparator) { + needs_sep_fix = true; + } + + if (cur_part_ofs != 0) { + break; + } + + R_UNLESS(!PathNormalizer::IsSeparator(path[i + 1]), fs::ResultInvalidPathFormat()); + + R_TRY(CheckSharedName(path + 2, i - 2)); + + cur_part_ofs = i + 1; + } + if (path[i] == '$' || path[i] == StringTraits::DriveSeparator) { + R_UNLESS(cur_part_ofs != 0, fs::ResultInvalidCharacter()); + R_UNLESS(PathNormalizer::IsAnySeparator(path[i + 1]) || PathNormalizer::IsNullTerminator(path[i + 1]), fs::ResultInvalidPathFormat()); + trimmed_path = path + i + 1; + break; + } + } + + if (trimmed_path == path_start) { + int tr_part_ofs = 0; + int i; + for (i = 2; !PathNormalizer::IsNullTerminator(path[i]); ++i) { + if (PathNormalizer::IsAnySeparator(path[i])) { + if (tr_part_ofs != 0) { + R_TRY(CheckSharedName(path + tr_part_ofs, i - tr_part_ofs)); + trimmed_path = path + i; + break; + } + + R_UNLESS(!PathNormalizer::IsSeparator(path[i + 1]), fs::ResultInvalidPathFormat()); + + R_TRY(CheckSharedName(path + 2, i - 2)); + + cur_part_ofs = i + 1; + } + } + + if (tr_part_ofs != 0 && trimmed_path == path_start) { + R_TRY(CheckSharedName(path + tr_part_ofs, i - tr_part_ofs)); + trimmed_path = path + i; + } + } + + const size_t win_path_len = trimmed_path - path; + const bool prepend_sep = prefix_len != 0 || skipped_mount; + if (out != nullptr) { + R_UNLESS(win_path_len <= max_out_size, fs::ResultTooLongPath()); + if (prepend_sep) { + *(out++) = StringTraits::DirectorySeparator; + } + std::memcpy(out, path, win_path_len); + out[0] = StringTraits::AlternateDirectorySeparator; + out[1] = StringTraits::AlternateDirectorySeparator; + if (needs_sep_fix) { + for (size_t i = 2; i < win_path_len; ++i) { + if (PathNormalizer::IsSeparator(out[i])) { + out[i] = StringTraits::AlternateDirectorySeparator; + } + } + } + } + *out_path = trimmed_path; + *out_windows_path_len = win_path_len + (prepend_sep ? 1 : 0); + } else { + *out_path = trimmed_path; + } + + return ResultSuccess(); + } + + Result SkipWindowsPath(const char **out_path, bool *out_normalized, const char *path, bool has_mount_name) { + size_t windows_path_len; + return ParseWindowsPath(out_path, nullptr, std::addressof(windows_path_len), out_normalized, path, 0, has_mount_name); + } + + Result ParseMountName(const char **out_path, char *out, size_t *out_mount_name_len, const char *path, size_t max_out_size) { + /* Decide on a start. */ + const char *start = PathNormalizer::IsSeparator(path[0]) ? path + 1 : path; + + /* Find the end of the mount name. */ + const char *cur; + for (cur = start; cur < start + MountNameLengthMax + 1; ++cur) { + if (*cur == StringTraits::DriveSeparator) { + ++cur; + break; + } + if (PathNormalizer::IsSeparator(*cur)) { + *out_path = path; + *out_mount_name_len = 0; + return ResultSuccess(); + } + } + R_UNLESS(start < cur - 1, fs::ResultInvalidPathFormat()); + R_UNLESS(cur[-1] == StringTraits::DriveSeparator, fs::ResultInvalidPathFormat()); + + /* Check the mount name doesn't contain a dot. */ + if (cur != start) { + for (const char *p = start; p < cur; ++p) { + R_UNLESS(*p != StringTraits::Dot, fs::ResultInvalidCharacter()); + } + } + + const size_t mount_name_len = cur - path; + if (out != nullptr) { + R_UNLESS(mount_name_len <= max_out_size, fs::ResultTooLongPath()); + std::memcpy(out, path, mount_name_len); + } + *out_path = cur; + *out_mount_name_len = mount_name_len; + + return ResultSuccess(); + } + + Result SkipMountName(const char **out_path, const char *path) { + size_t mount_name_len; + return ParseMountName(out_path, nullptr, std::addressof(mount_name_len), path, 0); + } + + bool IsParentDirectoryPathReplacementNeeded(const char *path) { + if (!PathNormalizer::IsAnySeparator(path[0])) { + return false; + } + + for (auto i = 0; !PathNormalizer::IsNullTerminator(path[i]); ++i) { + if (path[i + 0] == StringTraits::AlternateDirectorySeparator && + path[i + 1] == StringTraits::Dot && + path[i + 2] == StringTraits::Dot && + (PathNormalizer::IsAnySeparator(path[i + 3]) || PathNormalizer::IsNullTerminator(path[i + 3]))) + { + return true; + } + + if (PathNormalizer::IsAnySeparator(path[i + 0]) && + path[i + 1] == StringTraits::Dot && + path[i + 2] == StringTraits::Dot && + path[i + 3] == StringTraits::AlternateDirectorySeparator) + { + return true; + } + } + + return false; + } + + void ReplaceParentDirectoryPath(char *dst, const char *src) { + dst[0] = StringTraits::DirectorySeparator; + + int i = 1; + while (!PathNormalizer::IsNullTerminator(src[i])) { + if (PathNormalizer::IsAnySeparator(src[i - 1]) && + src[i + 0] == StringTraits::Dot && + src[i + 1] == StringTraits::Dot && + PathNormalizer::IsAnySeparator(src[i + 2])) + { + dst[i - 1] = StringTraits::DirectorySeparator; + dst[i + 0] = StringTraits::Dot; + dst[i + 1] = StringTraits::Dot; + dst[i - 2] = StringTraits::DirectorySeparator; + i += 3; + } else { + if (src[i - 1] == StringTraits::AlternateDirectorySeparator && + src[i + 0] == StringTraits::Dot && + src[i + 1] == StringTraits::Dot && + PathNormalizer::IsNullTerminator(src[i + 2])) + { + dst[i - 1] = StringTraits::DirectorySeparator; + dst[i + 0] = StringTraits::Dot; + dst[i + 1] = StringTraits::Dot; + i += 2; + break; + } + + dst[i] = src[i]; + ++i; + } + } + + dst[i] = StringTraits::NullTerminator; + } + + } + + Result PathNormalizer::Normalize(char *out, size_t *out_len, const char *path, size_t max_out_size, bool unc_preserved, bool has_mount_name) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(out_len != nullptr); + AMS_ASSERT(path != nullptr); + + /* If we should, handle the mount name. */ + size_t prefix_len = 0; + if (has_mount_name) { + size_t mount_name_len = 0; + R_TRY(ParseMountName(std::addressof(path), out, std::addressof(mount_name_len), path, max_out_size)); + prefix_len += mount_name_len; + } + + /* Deal with unc. */ + bool is_unc_path = false; + if (unc_preserved) { + const char * const path_start = path; + size_t windows_path_len = 0; + R_TRY(ParseWindowsPath(std::addressof(path), out + prefix_len, std::addressof(windows_path_len), nullptr, path, max_out_size, has_mount_name)); + prefix_len += windows_path_len; + is_unc_path = path != path_start; + } + + /* Paths must start with / */ + R_UNLESS(prefix_len != 0 || IsSeparator(path[0]), fs::ResultInvalidPathFormat()); + + /* Check if parent directory path replacement is needed. */ + std::unique_ptr replacement_path; + if (IsParentDirectoryPathReplacementNeeded(path)) { + /* Allocate a buffer to hold the replacement path. */ + replacement_path = fs::impl::MakeUnique(EntryNameLengthMax + 1); + R_UNLESS(replacement_path != nullptr, fs::ResultAllocationFailureInNew()); + + /* Replace the path. */ + ReplaceParentDirectoryPath(replacement_path.get(), path); + + /* Set path to be the replacement path. */ + path = replacement_path.get(); + } + + bool skip_next_sep = false; + size_t i = 0; + size_t len = prefix_len; + + while (!IsNullTerminator(path[i])) { + if (IsSeparator(path[i])) { + /* Swallow separators. */ + while (IsSeparator(path[++i])) { } + if (IsNullTerminator(path[i])) { + break; + } + + /* Handle skip if needed */ + if (!skip_next_sep) { + if (len + 1 == max_out_size) { + out[len] = StringTraits::NullTerminator; + *out_len = len; + return fs::ResultTooLongPath(); + } + + out[len++] = StringTraits::DirectorySeparator; + } + skip_next_sep = false; + } + + /* See length of current dir. */ + size_t dir_len = 0; + while (!IsSeparator(path[i + dir_len]) && !IsNullTerminator(path[i + dir_len])) { + ++dir_len; + } + + if (IsCurrentDirectory(path + i)) { + skip_next_sep = true; + } else if (IsParentDirectory(path + i)) { + AMS_ASSERT(IsSeparator(out[len - 1])); + if (!is_unc_path) { + AMS_ASSERT(IsSeparator(out[prefix_len])); + } + + /* Walk up a directory. */ + if (len == prefix_len + 1) { + R_UNLESS(is_unc_path, fs::ResultDirectoryUnobtainable()); + --len; + } else { + len -= 2; + do { + if (IsSeparator(out[len])) { + break; + } + --len; + } while (len != prefix_len); + } + + if (!is_unc_path) { + AMS_ASSERT(IsSeparator(out[prefix_len])); + } + + AMS_ASSERT(len < max_out_size); + } else { + /* Copy, possibly truncating. */ + if (len + dir_len + 1 <= max_out_size) { + for (size_t j = 0; j < dir_len; ++j) { + out[len++] = path[i+j]; + } + } else { + const size_t copy_len = max_out_size - 1 - len; + for (size_t j = 0; j < copy_len; ++j) { + out[len++] = path[i+j]; + } + out[len] = StringTraits::NullTerminator; + *out_len = len; + return fs::ResultTooLongPath(); + } + } + + i += dir_len; + } + + if (skip_next_sep) { + --len; + } + + if (!is_unc_path && len == prefix_len && max_out_size > len) { + out[len++] = StringTraits::DirectorySeparator; + } + + R_UNLESS(max_out_size >= len - 1, fs::ResultTooLongPath()); + + /* Null terminate. */ + out[len] = StringTraits::NullTerminator; + *out_len = len; + + /* Assert normalized. */ + { + bool normalized = false; + const auto is_norm_result = IsNormalized(std::addressof(normalized), out, unc_preserved, has_mount_name); + AMS_ASSERT(R_SUCCEEDED(is_norm_result)); + AMS_ASSERT(normalized); + } + + return ResultSuccess(); + } + + Result PathNormalizer::IsNormalized(bool *out, const char *path, bool unc_preserved, bool has_mount_name) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(path != nullptr); + + /* Save the start of the path. */ + const char *path_start = path; + + /* If we should, skip the mount name. */ + if (has_mount_name) { + R_TRY(SkipMountName(std::addressof(path), path)); + R_UNLESS(IsSeparator(*path), fs::ResultInvalidPathFormat()); + } + + /* If we should, handle unc. */ + bool is_unc_path = false; + if (unc_preserved) { + path_start = path; + + /* Skip the windows path. */ + bool normalized_windows = false; + R_TRY(SkipWindowsPath(std::addressof(path), std::addressof(normalized_windows), path, has_mount_name)); + + /* If we're not windows-normalized, we're not normalized. */ + if (!normalized_windows) { + *out = false; + return ResultSuccess(); + } + + /* Handle the case where we're dealing with a unc path. */ + if (path != path_start) { + is_unc_path = true; + if (IsSeparator(path_start[0]) && IsSeparator(path_start[1])) { + *out = false; + return ResultSuccess(); + } + + if (IsNullTerminator(path[0])) { + *out = true; + return ResultSuccess(); + } + } + } + + /* Check if parent directory path replacement is needed. */ + if (IsParentDirectoryPathReplacementNeeded(path)) { + *out = false; + return ResultSuccess(); + } + + /* Nintendo uses a state machine here. */ + enum class PathState { + Start, + Normal, + FirstSeparator, + Separator, + CurrentDir, + ParentDir, + }; + + PathState state = PathState::Start; + for (const char *cur = path; *cur != StringTraits::NullTerminator; ++cur) { + const char c = *cur; + switch (state) { + case PathState::Start: + if (IsSeparator(c)) { + state = PathState::FirstSeparator; + } else { + R_UNLESS(path != path_start, fs::ResultInvalidPathFormat()); + if (c == StringTraits::Dot) { + state = PathState::CurrentDir; + } else { + state = PathState::Normal; + } + } + break; + case PathState::Normal: + if (IsSeparator(c)) { + state = PathState::Separator; + } + break; + case PathState::FirstSeparator: + case PathState::Separator: + if (IsSeparator(c)) { + *out = false; + return ResultSuccess(); + } else if (c == StringTraits::Dot) { + state = PathState::CurrentDir; + } else { + state = PathState::Normal; + } + break; + case PathState::CurrentDir: + if (IsSeparator(c)) { + *out = false; + return ResultSuccess(); + } else if (c == StringTraits::Dot) { + state = PathState::ParentDir; + } else { + state = PathState::Normal; + } + break; + case PathState::ParentDir: + if (IsSeparator(c)) { + *out = false; + return ResultSuccess(); + } else { + state = PathState::Normal; + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + switch (state) { + case PathState::Start: + return fs::ResultInvalidPathFormat(); + case PathState::Normal: + *out = true; + break; + case PathState::FirstSeparator: + *out = !is_unc_path; + break; + case PathState::CurrentDir: + case PathState::ParentDir: + case PathState::Separator: + *out = false; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/fs/fs_path_tool.cpp b/libraries/libstratosphere/source/fs/fs_path_tool.cpp deleted file mode 100644 index fdd64dbdc..000000000 --- a/libraries/libstratosphere/source/fs/fs_path_tool.cpp +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (c) 2018-2020 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include - -namespace ams::fs { - - - Result PathTool::Normalize(char *out, size_t *out_len, const char *src, size_t max_out_size, bool unc_preserved) { - /* Paths must start with / */ - R_UNLESS(IsSeparator(src[0]), fs::ResultInvalidPathFormat()); - - bool skip_next_sep = false; - size_t i = 0; - size_t len = 0; - - while (!IsNullTerminator(src[i])) { - if (IsSeparator(src[i])) { - /* Swallow separators. */ - while (IsSeparator(src[++i])) { } - if (IsNullTerminator(src[i])) { - break; - } - - /* Handle skip if needed */ - if (!skip_next_sep) { - if (len + 1 == max_out_size) { - out[len] = StringTraits::NullTerminator; - if (out_len != nullptr) { - *out_len = len; - } - return fs::ResultTooLongPath(); - } - - out[len++] = StringTraits::DirectorySeparator; - - if (unc_preserved && len == 1) { - while (len < i) { - if (len + 1 == max_out_size) { - out[len] = StringTraits::NullTerminator; - if (out_len != nullptr) { - *out_len = len; - } - return fs::ResultTooLongPath(); - } - out[len++] = StringTraits::DirectorySeparator; - } - } - } - skip_next_sep = false; - } - - /* See length of current dir. */ - size_t dir_len = 0; - while (!IsSeparator(src[i+dir_len]) && !IsNullTerminator(src[i+dir_len])) { - dir_len++; - } - - if (IsCurrentDirectory(&src[i])) { - skip_next_sep = true; - } else if (IsParentDirectory(&src[i])) { - AMS_ABORT_UNLESS(IsSeparator(out[0])); - AMS_ABORT_UNLESS(IsSeparator(out[len - 1])); - R_UNLESS(len != 1, fs::ResultDirectoryUnobtainable()); - - /* Walk up a directory. */ - len -= 2; - while (!IsSeparator(out[len])) { - len--; - } - } else { - /* Copy, possibly truncating. */ - if (len + dir_len + 1 <= max_out_size) { - for (size_t j = 0; j < dir_len; j++) { - out[len++] = src[i+j]; - } - } else { - const size_t copy_len = max_out_size - 1 - len; - for (size_t j = 0; j < copy_len; j++) { - out[len++] = src[i+j]; - } - out[len] = StringTraits::NullTerminator; - if (out_len != nullptr) { - *out_len = len; - } - return fs::ResultTooLongPath(); - } - } - - i += dir_len; - } - - if (skip_next_sep) { - len--; - } - - if (len == 0 && max_out_size) { - out[len++] = StringTraits::DirectorySeparator; - } - - R_UNLESS(max_out_size >= len - 1, fs::ResultTooLongPath()); - - /* Null terminate. */ - out[len] = StringTraits::NullTerminator; - if (out_len != nullptr) { - *out_len = len; - } - - /* Assert normalized. */ - bool normalized = false; - AMS_ABORT_UNLESS(unc_preserved || (R_SUCCEEDED(IsNormalized(&normalized, out)) && normalized)); - - return ResultSuccess(); - } - - Result PathTool::IsNormalized(bool *out, const char *path) { - /* Nintendo uses a state machine here. */ - enum class PathState { - Start, - Normal, - FirstSeparator, - Separator, - CurrentDir, - ParentDir, - WindowsDriveLetter, - }; - - PathState state = PathState::Start; - - for (const char *cur = path; *cur != StringTraits::NullTerminator; cur++) { - const char c = *cur; - switch (state) { - case PathState::Start: - if (IsWindowsDriveCharacter(c)) { - state = PathState::WindowsDriveLetter; - } else if (IsSeparator(c)) { - state = PathState::FirstSeparator; - } else { - return fs::ResultInvalidPathFormat(); - } - break; - case PathState::Normal: - if (IsSeparator(c)) { - state = PathState::Separator; - } - break; - case PathState::FirstSeparator: - case PathState::Separator: - if (IsSeparator(c)) { - *out = false; - return ResultSuccess(); - } else if (IsDot(c)) { - state = PathState::CurrentDir; - } else { - state = PathState::Normal; - } - break; - case PathState::CurrentDir: - if (IsSeparator(c)) { - *out = false; - return ResultSuccess(); - } else if (IsDot(c)) { - state = PathState::ParentDir; - } else { - state = PathState::Normal; - } - break; - case PathState::ParentDir: - if (IsSeparator(c)) { - *out = false; - return ResultSuccess(); - } else { - state = PathState::Normal; - } - break; - case PathState::WindowsDriveLetter: - if (IsDriveSeparator(c)) { - *out = true; - return ResultSuccess(); - } else { - return fs::ResultInvalidPathFormat(); - } - break; - } - } - - switch (state) { - case PathState::Start: - case PathState::WindowsDriveLetter: - return fs::ResultInvalidPathFormat(); - case PathState::FirstSeparator: - case PathState::Normal: - *out = true; - break; - case PathState::CurrentDir: - case PathState::ParentDir: - case PathState::Separator: - *out = false; - break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - return ResultSuccess(); - } - - bool PathTool::IsSubPath(const char *lhs, const char *rhs) { - AMS_ABORT_UNLESS(lhs != nullptr); - AMS_ABORT_UNLESS(rhs != nullptr); - - /* Special case certain paths. */ - if (IsSeparator(lhs[0]) && !IsSeparator(lhs[1]) && IsSeparator(rhs[0]) && IsSeparator(rhs[1])) { - return false; - } - if (IsSeparator(rhs[0]) && !IsSeparator(rhs[1]) && IsSeparator(lhs[0]) && IsSeparator(lhs[1])) { - return false; - } - if (IsSeparator(lhs[0]) && IsNullTerminator(lhs[1]) && IsSeparator(rhs[0]) && !IsNullTerminator(rhs[1])) { - return true; - } - if (IsSeparator(rhs[0]) && IsNullTerminator(rhs[1]) && IsSeparator(lhs[0]) && !IsNullTerminator(lhs[1])) { - return true; - } - - /* Check subpath. */ - for (size_t i = 0; /* No terminating condition */; i++) { - if (IsNullTerminator(lhs[i])) { - return IsSeparator(rhs[i]); - } else if (IsNullTerminator(rhs[i])) { - return IsSeparator(lhs[i]); - } else if (lhs[i] != rhs[i]) { - return false; - } - } - } - -} diff --git a/libraries/libstratosphere/source/fs/fs_path_utils.cpp b/libraries/libstratosphere/source/fs/fs_path_utils.cpp index db15d5b8f..8239e713c 100644 --- a/libraries/libstratosphere/source/fs/fs_path_utils.cpp +++ b/libraries/libstratosphere/source/fs/fs_path_utils.cpp @@ -17,31 +17,114 @@ namespace ams::fs { - Result VerifyPath(const char *path, size_t max_path_len, size_t max_name_len) { - const char *cur = path; - size_t name_len = 0; + namespace { - for (size_t path_len = 0; path_len <= max_path_len && name_len <= max_name_len; path_len++) { - const char c = *(cur++); + class PathVerifier { + private: + u32 invalid_chars[6]; + u32 separators[2]; + public: + PathVerifier() { + /* Convert all invalid characters. */ + u32 *dst_invalid = this->invalid_chars; + for (const char *cur = ":*?<>|"; *cur != '\x00'; ++cur) { + AMS_ASSERT(dst_invalid < std::end(this->invalid_chars)); + const auto result = util::ConvertCharacterUtf8ToUtf32(dst_invalid, cur); + AMS_ASSERT(result == util::CharacterEncodingResult_Success); + ++dst_invalid; + } + AMS_ASSERT(dst_invalid == std::end(this->invalid_chars)); - /* If terminated, we're done. */ - R_SUCCEED_IF(PathTool::IsNullTerminator(c)); + /* Convert all separators. */ + u32 *dst_sep = this->separators; + for (const char *cur = "/\\"; *cur != '\x00'; ++cur) { + AMS_ASSERT(dst_sep < std::end(this->separators)); + const auto result = util::ConvertCharacterUtf8ToUtf32(dst_sep, cur); + AMS_ASSERT(result == util::CharacterEncodingResult_Success); + ++dst_sep; + } + AMS_ASSERT(dst_sep == std::end(this->separators)); + } - /* TODO: Nintendo converts the path from utf-8 to utf-32, one character at a time. */ - /* We should do this. */ + Result Verify(const char *path, int max_path_len, int max_name_len) const { + AMS_ASSERT(path != nullptr); - /* Banned characters: :*?<>| */ - R_UNLESS((c != ':' && c != '*' && c != '?' && c != '<' && c != '>' && c != '|'), fs::ResultInvalidCharacter()); + auto cur = path; + auto name_len = 0; - name_len++; - /* Check for separator. */ - if (c == '\\' || c == '/') { - name_len = 0; - } + for (auto path_len = 0; path_len <= max_path_len && name_len <= max_name_len; ++path_len) { + /* We're done, if the path is terminated. */ + R_SUCCEED_IF(*cur == '\x00'); + /* Get the current utf-8 character. */ + util::CharacterEncodingResult result; + char char_buf[4] = {}; + result = util::PickOutCharacterFromUtf8String(char_buf, std::addressof(cur)); + R_UNLESS(result == util::CharacterEncodingResult_Success, fs::ResultInvalidCharacter()); + + /* Convert the current utf-8 character to utf-32. */ + u32 path_char = 0; + result = util::ConvertCharacterUtf8ToUtf32(std::addressof(path_char), char_buf); + R_UNLESS(result == util::CharacterEncodingResult_Success, fs::ResultInvalidCharacter()); + + /* Check if the character is invalid. */ + for (const auto invalid : this->invalid_chars) { + R_UNLESS(path_char != invalid, fs::ResultInvalidCharacter()); + } + + /* Increment name length. */ + ++name_len; + + /* Check for separator. */ + for (const auto sep : this->separators) { + if (path_char == sep) { + name_len = 0; + break; + } + } + } + + /* The path was too long. */ + return fs::ResultTooLongPath(); + } + }; + + PathVerifier g_path_verifier; + + } + + Result VerifyPath(const char *path, int max_path_len, int max_name_len) { + return g_path_verifier.Verify(path, max_path_len, max_name_len); + } + + bool IsSubPath(const char *lhs, const char *rhs) { + AMS_ASSERT(lhs != nullptr); + AMS_ASSERT(rhs != nullptr); + + /* Special case certain paths. */ + if (IsUnc(lhs) && !IsUnc(rhs)) { + return false; + } + if (!IsUnc(lhs) && IsUnc(rhs)) { + return false; + } + if (PathNormalizer::IsSeparator(lhs[0]) && PathNormalizer::IsNullTerminator(lhs[1]) && PathNormalizer::IsSeparator(rhs[0]) && !PathNormalizer::IsNullTerminator(rhs[1])) { + return true; + } + if (PathNormalizer::IsSeparator(rhs[0]) && PathNormalizer::IsNullTerminator(rhs[1]) && PathNormalizer::IsSeparator(lhs[0]) && !PathNormalizer::IsNullTerminator(lhs[1])) { + return true; } - return fs::ResultTooLongPath(); + /* Check subpath. */ + for (size_t i = 0; /* No terminating condition */; i++) { + if (PathNormalizer::IsNullTerminator(lhs[i])) { + return PathNormalizer::IsSeparator(rhs[i]); + } else if (PathNormalizer::IsNullTerminator(rhs[i])) { + return PathNormalizer::IsSeparator(lhs[i]); + } else if (lhs[i] != rhs[i]) { + return false; + } + } } } diff --git a/libraries/libstratosphere/source/fs/fsa/fs_mount_utils.cpp b/libraries/libstratosphere/source/fs/fsa/fs_mount_utils.cpp index 79c226526..aeb04ee83 100644 --- a/libraries/libstratosphere/source/fs/fsa/fs_mount_utils.cpp +++ b/libraries/libstratosphere/source/fs/fsa/fs_mount_utils.cpp @@ -24,9 +24,9 @@ namespace ams::fs::impl { const char *FindMountNameDriveSeparator(const char *path) { for (const char *cur = path; cur < path + MountNameLengthMax + 1; cur++) { - if (PathTool::IsDriveSeparator(*cur)) { + if (*cur == StringTraits::DriveSeparator) { return cur; - } else if (PathTool::IsNullTerminator(*cur)) { + } else if (PathNormalizer::IsNullTerminator(*cur)) { break; } } @@ -35,7 +35,7 @@ namespace ams::fs::impl { Result GetMountNameAndSubPath(MountName *out_mount_name, const char **out_sub_path, const char *path) { /* Handle the Host-path case. */ - if (PathTool::IsWindowsAbsolutePath(path) || PathTool::IsUnc(path)) { + if (fs::IsWindowsDrive(path) || fs::IsUnc(path)) { std::strncpy(out_mount_name->str, HostRootFileSystemMountName, MountNameLengthMax); out_mount_name->str[MountNameLengthMax] = '\x00'; return ResultSuccess(); @@ -51,8 +51,8 @@ namespace ams::fs::impl { /* Ensure the result sub-path is valid. */ const char *sub_path = drive_separator + 1; - R_UNLESS(!PathTool::IsNullTerminator(sub_path[0]), fs::ResultInvalidMountName()); - R_UNLESS(PathTool::IsAnySeparator(sub_path[0]), fs::ResultInvalidPathFormat()); + R_UNLESS(!PathNormalizer::IsNullTerminator(sub_path[0]), fs::ResultInvalidMountName()); + R_UNLESS(PathNormalizer::IsAnySeparator(sub_path[0]), fs::ResultInvalidPathFormat()); /* Set output. */ std::memcpy(out_mount_name->str, path, len); @@ -64,17 +64,17 @@ namespace ams::fs::impl { } bool IsValidMountName(const char *name) { - if (PathTool::IsNullTerminator(*name)) { + if (PathNormalizer::IsNullTerminator(name[0])) { return false; } - if (PathTool::IsWindowsDriveCharacter(name[0]) && PathTool::IsNullTerminator(name[1])) { + if ((('a' <= name[0] && name[0] <= 'z') || ('A' <= name[0] && name[0] <= 'Z')) && PathNormalizer::IsNullTerminator(name[1])) { return false; } size_t len = 0; - for (const char *cur = name; !PathTool::IsNullTerminator(*cur); cur++) { - if (PathTool::IsDriveSeparator(*cur) || PathTool::IsSeparator(*cur)) { + for (const char *cur = name; !PathNormalizer::IsNullTerminator(*cur); cur++) { + if (*cur == StringTraits::DriveSeparator || PathNormalizer::IsSeparator(*cur)) { return false; } @@ -87,10 +87,6 @@ namespace ams::fs::impl { return true; } - bool IsWindowsDrive(const char *name) { - return PathTool::IsWindowsAbsolutePath(name); - } - bool IsReservedMountName(const char *name) { return name[0] == ReservedMountNamePrefixCharacter; } diff --git a/libraries/libstratosphere/source/fssrv/fssrv_filesystem_interface_adapter.cpp b/libraries/libstratosphere/source/fssrv/fssrv_filesystem_interface_adapter.cpp index 23040e25c..8e612bf02 100644 --- a/libraries/libstratosphere/source/fssrv/fssrv_filesystem_interface_adapter.cpp +++ b/libraries/libstratosphere/source/fssrv/fssrv_filesystem_interface_adapter.cpp @@ -216,7 +216,7 @@ namespace ams::fssrv::impl { R_UNLESS(old_normalizer.GetPath() != nullptr, old_normalizer.GetResult()); R_UNLESS(new_normalizer.GetPath() != nullptr, new_normalizer.GetResult()); - const bool is_subpath = fssystem::PathTool::IsSubPath(old_normalizer.GetPath(), new_normalizer.GetPath()); + const bool is_subpath = fs::IsSubPath(old_normalizer.GetPath(), new_normalizer.GetPath()); R_UNLESS(!is_subpath, fs::ResultDirectoryNotRenamable()); return this->base_fs->RenameFile(old_normalizer.GetPath(), new_normalizer.GetPath()); diff --git a/libraries/libstratosphere/source/fssrv/fssrv_path_normalizer.cpp b/libraries/libstratosphere/source/fssrv/fssrv_path_normalizer.cpp index 8b478c656..d58623841 100644 --- a/libraries/libstratosphere/source/fssrv/fssrv_path_normalizer.cpp +++ b/libraries/libstratosphere/source/fssrv/fssrv_path_normalizer.cpp @@ -18,51 +18,36 @@ namespace ams::fssrv { Result PathNormalizer::Normalize(const char **out_path, Buffer *out_buf, const char *path, bool preserve_unc, bool preserve_tail_sep, bool has_mount_name) { + /* Check pre-conditions. */ + AMS_ASSERT(out_path != nullptr); + AMS_ASSERT(out_buf != nullptr); + /* Clear output. */ *out_path = nullptr; *out_buf = Buffer(); - /* Find start of path. */ - const char *path_start = path; - if (has_mount_name) { - while (path_start < path + fs::MountNameLengthMax + 1) { - if (fssystem::PathTool::IsNullTerminator(*path_start)) { - break; - } else if (fssystem::PathTool::IsDriveSeparator(*(path_start++))) { - break; - } - } - R_UNLESS(path < path_start - 1, fs::ResultInvalidPath()); - R_UNLESS(fssystem::PathTool::IsDriveSeparator(*(path_start - 1)), fs::ResultInvalidPath()); - } - /* Check if we're normalized. */ bool normalized = false; - R_TRY(fssystem::PathTool::IsNormalized(&normalized, path_start)); + R_TRY(fs::PathNormalizer::IsNormalized(std::addressof(normalized), path, preserve_unc, has_mount_name)); if (normalized) { /* If we're already normalized, no allocation is needed. */ *out_path = path; } else { /* Allocate a new buffer. */ - auto buffer = std::make_unique(fs::EntryNameLengthMax + 1); + auto buffer = fs::impl::MakeUnique(fs::EntryNameLengthMax + 1); R_UNLESS(buffer != nullptr, fs::ResultAllocationFailureInPathNormalizer()); - /* Copy in mount name. */ - const size_t mount_name_len = path_start - path; - std::memcpy(buffer.get(), path, mount_name_len); - /* Generate normalized path. */ size_t normalized_len = 0; - R_TRY(fssystem::PathTool::Normalize(buffer.get() + mount_name_len, &normalized_len, path_start, fs::EntryNameLengthMax + 1 - mount_name_len, preserve_unc)); + R_TRY(fs::PathNormalizer::Normalize(buffer.get(), std::addressof(normalized_len), path, fs::EntryNameLengthMax + 1, preserve_unc, has_mount_name)); /* Preserve the tail separator, if we should. */ if (preserve_tail_sep) { - if (fssystem::PathTool::IsSeparator(path[strnlen(path, fs::EntryNameLengthMax) - 1])) { - /* Nintendo doesn't actually validate this. */ - R_UNLESS(mount_name_len + normalized_len < fs::EntryNameLengthMax, fs::ResultTooLongPath()); - buffer[mount_name_len + normalized_len] = fssystem::StringTraits::DirectorySeparator; - buffer[mount_name_len + normalized_len + 1] = fssystem::StringTraits::NullTerminator; + if (fs::PathNormalizer::IsSeparator(path[strnlen(path, fs::EntryNameLengthMax) - 1]) && !fs::PathNormalizer::IsSeparator(buffer[normalized_len - 1])) { + AMS_ASSERT(normalized_len < fs::EntryNameLengthMax); + buffer[normalized_len] = fs::StringTraits::DirectorySeparator; + buffer[normalized_len + 1] = fs::StringTraits::NullTerminator; } } diff --git a/libraries/libstratosphere/source/fssystem/fssystem_directory_redirection_filesystem.cpp b/libraries/libstratosphere/source/fssystem/fssystem_directory_redirection_filesystem.cpp index 45eb64a2e..abbcb899f 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_directory_redirection_filesystem.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_directory_redirection_filesystem.cpp @@ -35,10 +35,10 @@ namespace ams::fssystem { DirectoryRedirectionFileSystem::~DirectoryRedirectionFileSystem() { if (this->before_dir != nullptr) { - std::free(this->before_dir); + fs::impl::Deallocate(this->before_dir, this->before_dir_len); } if (this->after_dir != nullptr) { - std::free(this->after_dir); + fs::impl::Deallocate(this->after_dir, this->after_dir_len); } } @@ -53,26 +53,26 @@ namespace ams::fssystem { /* Normalize the path. */ char normalized_path[fs::EntryNameLengthMax + 2]; size_t normalized_path_len; - R_TRY(PathTool::Normalize(normalized_path, &normalized_path_len, dir, sizeof(normalized_path), this->IsUncPreserved())); + R_TRY(fs::PathNormalizer::Normalize(normalized_path, &normalized_path_len, dir, sizeof(normalized_path), this->IsUncPreserved(), false)); /* Ensure terminating '/' */ - if (!PathTool::IsSeparator(normalized_path[normalized_path_len - 1])) { - AMS_ABORT_UNLESS(normalized_path_len + 2 <= sizeof(normalized_path)); - normalized_path[normalized_path_len] = StringTraits::DirectorySeparator; - normalized_path[normalized_path_len + 1] = StringTraits::NullTerminator; + if (!fs::PathNormalizer::IsSeparator(normalized_path[normalized_path_len - 1])) { + AMS_ASSERT(normalized_path_len + 2 < sizeof(normalized_path)); + normalized_path[normalized_path_len] = fs::StringTraits::DirectorySeparator; + normalized_path[normalized_path_len + 1] = fs::StringTraits::NullTerminator; - normalized_path_len++; + ++normalized_path_len; } /* Allocate new path. */ const size_t size = normalized_path_len + 1; - char *new_dir = static_cast(std::malloc(size)); + char *new_dir = static_cast(fs::impl::Allocate(size)); AMS_ABORT_UNLESS(new_dir != nullptr); /* TODO: custom ResultAllocationFailure? */ /* Copy path in. */ std::memcpy(new_dir, normalized_path, normalized_path_len); - new_dir[normalized_path_len] = StringTraits::NullTerminator; + new_dir[normalized_path_len] = fs::StringTraits::NullTerminator; /* Set output. */ *out = new_dir; @@ -82,20 +82,30 @@ namespace ams::fssystem { Result DirectoryRedirectionFileSystem::Initialize(const char *before, const char *after) { /* Normalize both directories. */ - this->GetNormalizedDirectoryPath(&this->before_dir, &this->before_dir_len, before); - this->GetNormalizedDirectoryPath(&this->after_dir, &this->after_dir_len, after); + this->GetNormalizedDirectoryPath(std::addressof(this->before_dir), std::addressof(this->before_dir_len), before); + this->GetNormalizedDirectoryPath(std::addressof(this->after_dir), std::addressof(this->after_dir_len), after); return ResultSuccess(); } Result DirectoryRedirectionFileSystem::ResolveFullPath(char *out, size_t out_size, const char *relative_path) { + /* Check pre-conditions. */ + AMS_ASSERT(relative_path[0] == '/'); + AMS_ASSERT(this->before_dir_len >= 2); + AMS_ASSERT(this->after_dir_len >= 2); + AMS_ASSERT(this->after_dir_len <= out_size); + AMS_ASSERT(fs::PathNormalizer::IsNullTerminator(this->before_dir[this->before_dir_len - 1])); + AMS_ASSERT(fs::PathNormalizer::IsSeparator(this->before_dir[this->before_dir_len - 2])); + AMS_ASSERT(fs::PathNormalizer::IsNullTerminator(this->after_dir[this->after_dir_len - 1])); + AMS_ASSERT(fs::PathNormalizer::IsSeparator(this->after_dir[this->after_dir_len - 2])); + /* Normalize the relative path. */ char normalized_rel_path[fs::EntryNameLengthMax + 1]; size_t normalized_rel_path_len; - R_TRY(PathTool::Normalize(normalized_rel_path, &normalized_rel_path_len, relative_path, sizeof(normalized_rel_path), this->IsUncPreserved())); + R_TRY(fs::PathNormalizer::Normalize(normalized_rel_path, std::addressof(normalized_rel_path_len), relative_path, sizeof(normalized_rel_path), this->IsUncPreserved(), false)); const bool is_prefixed = std::memcmp(normalized_rel_path, this->before_dir, this->before_dir_len - 2) == 0 && - (PathTool::IsSeparator(normalized_rel_path[this->before_dir_len - 2]) || PathTool::IsNullTerminator(normalized_rel_path[this->before_dir_len - 2])); + (fs::PathNormalizer::IsSeparator(normalized_rel_path[this->before_dir_len - 2]) || fs::PathNormalizer::IsNullTerminator(normalized_rel_path[this->before_dir_len - 2])); if (is_prefixed) { const size_t before_prefix_len = this->before_dir_len - 2; const size_t after_prefix_len = this->after_dir_len - 2; @@ -105,12 +115,12 @@ namespace ams::fssystem { /* Copy normalized path. */ std::memcpy(out, this->after_dir, after_prefix_len); std::memcpy(out + after_prefix_len, normalized_rel_path + before_prefix_len, normalized_rel_path_len - before_prefix_len); - out[final_str_len] = StringTraits::NullTerminator; + out[final_str_len] = fs::StringTraits::NullTerminator; } else { /* Path is not prefixed. */ R_UNLESS(normalized_rel_path_len + 1 <= out_size, fs::ResultTooLongPath()); std::memcpy(out, normalized_rel_path, normalized_rel_path_len); - out[normalized_rel_path_len] = StringTraits::NullTerminator; + out[normalized_rel_path_len] = fs::StringTraits::NullTerminator; } return ResultSuccess(); diff --git a/libraries/libstratosphere/source/fssystem/fssystem_directory_savedata_filesystem.cpp b/libraries/libstratosphere/source/fssystem/fssystem_directory_savedata_filesystem.cpp index ebc1b1e54..8f60adee9 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_directory_savedata_filesystem.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_directory_savedata_filesystem.cpp @@ -156,16 +156,16 @@ namespace ams::fssystem { Result DirectorySaveDataFileSystem::ResolveFullPath(char *out, size_t out_size, const char *relative_path) { R_UNLESS(strnlen(relative_path, fs::EntryNameLengthMax + 1) < fs::EntryNameLengthMax + 1, fs::ResultTooLongPath()); - R_UNLESS(PathTool::IsSeparator(relative_path[0]), fs::ResultInvalidPath()); + R_UNLESS(fs::PathNormalizer::IsSeparator(relative_path[0]), fs::ResultInvalidPath()); /* Copy working directory path. */ std::strncpy(out, WorkingDirectoryPath, out_size); - out[out_size - 1] = StringTraits::NullTerminator; + out[out_size - 1] = fs::StringTraits::NullTerminator; /* Normalize it. */ constexpr size_t WorkingDirectoryPathLength = sizeof(WorkingDirectoryPath) - 1; size_t normalized_length; - return PathTool::Normalize(out + WorkingDirectoryPathLength - 1, &normalized_length, relative_path, out_size - (WorkingDirectoryPathLength - 1)); + return fs::PathNormalizer::Normalize(out + WorkingDirectoryPathLength - 1, &normalized_length, relative_path, out_size - (WorkingDirectoryPathLength - 1)); } void DirectorySaveDataFileSystem::OnWritableFileClose() { @@ -186,7 +186,7 @@ namespace ams::fssystem { R_TRY(this->AllocateWorkBuffer(&work_buf, &work_buf_size, IdealWorkBufferSize)); /* Copy the directory recursively. */ - R_TRY(fssystem::CopyDirectoryRecursively(this->base_fs, save_fs, PathTool::RootPath, PathTool::RootPath, work_buf.get(), work_buf_size)); + R_TRY(fssystem::CopyDirectoryRecursively(this->base_fs, save_fs, fs::PathNormalizer::RootPath, fs::PathNormalizer::RootPath, work_buf.get(), work_buf_size)); return this->Commit(); } diff --git a/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system.cpp b/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system.cpp index add60023d..7802e59e8 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system.cpp @@ -241,7 +241,7 @@ namespace ams::fssystem { dir_entry.type = fs::DirectoryEntryType_File; dir_entry.file_size = this->parent->meta_data->GetEntry(this->cur_index)->size; std::strncpy(dir_entry.name, this->parent->meta_data->GetEntryName(this->cur_index), sizeof(dir_entry.name) - 1); - dir_entry.name[sizeof(dir_entry.name) - 1] = StringTraits::NullTerminator; + dir_entry.name[sizeof(dir_entry.name) - 1] = fs::StringTraits::NullTerminator; } *out_count = entry_count; @@ -349,11 +349,11 @@ namespace ams::fssystem { template Result PartitionFileSystemCore::DoGetEntryType(fs::DirectoryEntryType *out, const char *path) { /* Validate preconditions. */ - R_UNLESS(this->initialized, fs::ResultPreconditionViolation()); - R_UNLESS(PathTool::IsSeparator(path[0]), fs::ResultInvalidPathFormat()); + R_UNLESS(this->initialized, fs::ResultPreconditionViolation()); + R_UNLESS(fs::PathNormalizer::IsSeparator(path[0]), fs::ResultInvalidPathFormat()); /* Check if the path is for a directory. */ - if (std::strncmp(path, PathTool::RootPath, sizeof(PathTool::RootPath)) == 0) { + if (std::strncmp(path, fs::PathNormalizer::RootPath, sizeof(fs::PathNormalizer::RootPath)) == 0) { *out = fs::DirectoryEntryType_Directory; return ResultSuccess(); } @@ -384,8 +384,8 @@ namespace ams::fssystem { template Result PartitionFileSystemCore::DoOpenDirectory(std::unique_ptr *out_dir, const char *path, fs::OpenDirectoryMode mode) { /* Validate preconditions. */ - R_UNLESS(this->initialized, fs::ResultPreconditionViolation()); - R_UNLESS(std::strncmp(path, PathTool::RootPath, sizeof(PathTool::RootPath)) == 0, fs::ResultPathNotFound()); + R_UNLESS(this->initialized, fs::ResultPreconditionViolation()); + R_UNLESS(std::strncmp(path, fs::PathNormalizer::RootPath, sizeof(fs::PathNormalizer::RootPath)) == 0, fs::ResultPathNotFound()); /* Create and output the partition directory. */ std::unique_ptr directory = std::make_unique(this, mode); diff --git a/libraries/libstratosphere/source/fssystem/fssystem_subdirectory_filesystem.cpp b/libraries/libstratosphere/source/fssystem/fssystem_subdirectory_filesystem.cpp index 7ea7f723d..19fc51019 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_subdirectory_filesystem.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_subdirectory_filesystem.cpp @@ -33,7 +33,7 @@ namespace ams::fssystem { SubDirectoryFileSystem::~SubDirectoryFileSystem() { if (this->base_path != nullptr) { - std::free(this->base_path); + fs::impl::Deallocate(this->base_path, this->base_path_len); } } @@ -44,25 +44,25 @@ namespace ams::fssystem { /* Normalize the path. */ char normalized_path[fs::EntryNameLengthMax + 2]; size_t normalized_path_len; - R_TRY(PathTool::Normalize(normalized_path, &normalized_path_len, bp, sizeof(normalized_path), this->IsUncPreserved())); + R_TRY(fs::PathNormalizer::Normalize(normalized_path, std::addressof(normalized_path_len), bp, sizeof(normalized_path), this->IsUncPreserved())); /* Ensure terminating '/' */ - if (!PathTool::IsSeparator(normalized_path[normalized_path_len - 1])) { - AMS_ABORT_UNLESS(normalized_path_len + 2 <= sizeof(normalized_path)); - normalized_path[normalized_path_len] = StringTraits::DirectorySeparator; - normalized_path[normalized_path_len + 1] = StringTraits::NullTerminator; + if (!fs::PathNormalizer::IsSeparator(normalized_path[normalized_path_len - 1])) { + AMS_ASSERT(normalized_path_len + 2 < sizeof(normalized_path)); + normalized_path[normalized_path_len] = fs::StringTraits::DirectorySeparator; + normalized_path[normalized_path_len + 1] = fs::StringTraits::NullTerminator; - normalized_path_len++; + ++normalized_path_len; } /* Allocate new path. */ this->base_path_len = normalized_path_len + 1; - this->base_path = static_cast(std::malloc(this->base_path_len)); + this->base_path = static_cast(fs::impl::Allocate(this->base_path_len)); R_UNLESS(this->base_path != nullptr, fs::ResultAllocationFailureInSubDirectoryFileSystem()); /* Copy path in. */ std::memcpy(this->base_path, normalized_path, normalized_path_len); - this->base_path[normalized_path_len] = StringTraits::NullTerminator; + this->base_path[normalized_path_len] = fs::StringTraits::NullTerminator; return ResultSuccess(); } @@ -76,7 +76,7 @@ namespace ams::fssystem { /* Normalize it. */ const size_t prefix_size = this->base_path_len - 2; size_t normalized_len; - return PathTool::Normalize(out + prefix_size, &normalized_len, relative_path, out_size - prefix_size, this->IsUncPreserved()); + return fs::PathNormalizer::Normalize(out + prefix_size, std::addressof(normalized_len), relative_path, out_size - prefix_size, this->IsUncPreserved(), false); } } diff --git a/libraries/libstratosphere/source/fssystem/fssystem_utility.cpp b/libraries/libstratosphere/source/fssystem/fssystem_utility.cpp index 02783fc2a..760f3c25b 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_utility.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_utility.cpp @@ -31,15 +31,15 @@ namespace ams::fssystem { /* Normalize the path. */ char normalized_path[fs::EntryNameLengthMax + 1]; size_t normalized_path_len; - R_TRY(PathTool::Normalize(normalized_path, &normalized_path_len, path, sizeof(normalized_path))); + R_TRY(fs::PathNormalizer::Normalize(normalized_path, std::addressof(normalized_path_len), path, sizeof(normalized_path))); /* Repeatedly call CreateDirectory on each directory leading to the target. */ for (size_t i = 1; i < normalized_path_len; i++) { /* If we detect a separator, create the directory. */ - if (PathTool::IsSeparator(normalized_path[i])) { - normalized_path[i] = StringTraits::NullTerminator; + if (fs::PathNormalizer::IsSeparator(normalized_path[i])) { + normalized_path[i] = fs::StringTraits::NullTerminator; R_TRY(EnsureDirectory(fs, normalized_path)); - normalized_path[i] = StringTraits::DirectorySeparator; + normalized_path[i] = fs::StringTraits::DirectorySeparator; } } @@ -120,10 +120,10 @@ namespace ams::fssystem { /* Find previous separator, add null terminator */ char *cur = &dst_path_buf[len - 2]; - while (!PathTool::IsSeparator(*cur) && cur > dst_path_buf) { + while (!fs::PathNormalizer::IsSeparator(*cur) && cur > dst_path_buf) { cur--; } - cur[1] = StringTraits::NullTerminator; + cur[1] = fs::StringTraits::NullTerminator; return ResultSuccess(); }, diff --git a/libraries/libvapours/include/vapours/util.hpp b/libraries/libvapours/include/vapours/util.hpp index 2fae1e4fe..cac157fb2 100644 --- a/libraries/libvapours/include/vapours/util.hpp +++ b/libraries/libvapours/include/vapours/util.hpp @@ -40,5 +40,6 @@ #include #include #include +#include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_path_tool.hpp b/libraries/libvapours/include/vapours/util/util_character_encoding.hpp similarity index 58% rename from libraries/libstratosphere/include/stratosphere/fssystem/fssystem_path_tool.hpp rename to libraries/libvapours/include/vapours/util/util_character_encoding.hpp index b16d8ebc4..635b3e682 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_path_tool.hpp +++ b/libraries/libvapours/include/vapours/util/util_character_encoding.hpp @@ -13,14 +13,21 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + #pragma once -#include "../fs/fs_common.hpp" -#include "../fs/fs_path_tool.hpp" +#include +#include -namespace ams::fssystem { +namespace ams::util { - namespace StringTraits = ::ams::fs::StringTraits; + enum CharacterEncodingResult { + CharacterEncodingResult_Success = 0, + CharacterEncodingResult_InsufficientLength = 1, + CharacterEncodingResult_InvalidFormat = 2, + }; - using PathTool = ::ams::fs::PathTool; + CharacterEncodingResult ConvertCharacterUtf8ToUtf32(u32 *dst, const char *src); + + CharacterEncodingResult PickOutCharacterFromUtf8String(char *dst, const char **str); } diff --git a/libraries/libvapours/source/util/util_character_encoding.cpp b/libraries/libvapours/source/util/util_character_encoding.cpp new file mode 100644 index 000000000..fb9f6b80b --- /dev/null +++ b/libraries/libvapours/source/util/util_character_encoding.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::util { + + namespace { + + constexpr inline int8_t Utf8NBytesInnerTable[0x100 + 1] = { + -1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8, + }; + + constexpr inline const char * const Utf8NBytesTable = static_cast(static_cast(Utf8NBytesInnerTable + 1)); + + } + + CharacterEncodingResult ConvertCharacterUtf8ToUtf32(u32 *dst, const char *src) { + /* Check pre-conditions. */ + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(src != nullptr); + + /* Perform the conversion. */ + const unsigned char *p = reinterpret_cast(src); + switch (Utf8NBytesTable[p[0]]) { + case 1: + *dst = static_cast(p[0]); + return CharacterEncodingResult_Success; + case 2: + if ((p[0] & 0x1E) != 0) { + if (Utf8NBytesTable[p[1]] == 0) { + *dst = (static_cast(p[0] & 0x1F) << 6) | (static_cast(p[1] & 0x3F) << 0); + return CharacterEncodingResult_Success; + } + } + break; + case 3: + if (Utf8NBytesTable[p[1]] == 0 && Utf8NBytesTable[p[2]] == 0) { + const u32 c = (static_cast(p[0] & 0xF) << 12) | (static_cast(p[1] & 0x3F) << 6) | (static_cast(p[2] & 0x3F) << 0); + if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) { + *dst = c; + return CharacterEncodingResult_Success; + } + } + return CharacterEncodingResult_InvalidFormat; + case 4: + if (Utf8NBytesTable[p[1]] == 0 && Utf8NBytesTable[p[2]] == 0 && Utf8NBytesTable[p[3]] == 0) { + const u32 c = (static_cast(p[0] & 0x7) << 18) | (static_cast(p[1] & 0x3F) << 12) | (static_cast(p[2] & 0x3F) << 6) | (static_cast(p[3] & 0x3F) << 0); + if (c >= 0x10000 && c < 0x110000) { + *dst = c; + return CharacterEncodingResult_Success; + } + } + return CharacterEncodingResult_InvalidFormat; + default: + break; + } + + /* We failed to convert. */ + return CharacterEncodingResult_InvalidFormat; + } + + CharacterEncodingResult PickOutCharacterFromUtf8String(char *dst, const char **str) { + /* Check pre-conditions. */ + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(str != nullptr); + AMS_ASSERT(*str != nullptr); + + /* Clear the output. */ + dst[0] = 0; + dst[1] = 0; + dst[2] = 0; + dst[3] = 0; + + /* Perform the conversion. */ + const unsigned char *p = reinterpret_cast(*str); + u32 c = *p; + switch (Utf8NBytesTable[c]) { + case 1: + dst[0] = (*str)[0]; + ++(*str); + break; + case 2: + if ((p[0] & 0x1E) != 0) { + if (Utf8NBytesTable[p[1]] == 0) { + c = (static_cast(p[0] & 0x1F) << 6) | (static_cast(p[1] & 0x3F) << 0); + dst[0] = (*str)[0]; + dst[1] = (*str)[1]; + (*str) += 2; + break; + } + } + return CharacterEncodingResult_InvalidFormat; + case 3: + if (Utf8NBytesTable[p[1]] == 0 && Utf8NBytesTable[p[2]] == 0) { + c = (static_cast(p[0] & 0xF) << 12) | (static_cast(p[1] & 0x3F) << 6) | (static_cast(p[2] & 0x3F) << 0); + if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) { + dst[0] = (*str)[0]; + dst[1] = (*str)[1]; + dst[2] = (*str)[2]; + (*str) += 3; + break; + } + } + return CharacterEncodingResult_InvalidFormat; + case 4: + if (Utf8NBytesTable[p[1]] == 0 && Utf8NBytesTable[p[2]] == 0 && Utf8NBytesTable[p[3]] == 0) { + c = (static_cast(p[0] & 0x7) << 18) | (static_cast(p[1] & 0x3F) << 12) | (static_cast(p[2] & 0x3F) << 6) | (static_cast(p[3] & 0x3F) << 0); + if (c >= 0x10000 && c < 0x110000) { + dst[0] = (*str)[0]; + dst[1] = (*str)[1]; + dst[2] = (*str)[2]; + dst[3] = (*str)[3]; + (*str) += 4; + break; + } + } + return CharacterEncodingResult_InvalidFormat; + default: + return CharacterEncodingResult_InvalidFormat; + } + + return CharacterEncodingResult_Success; + } + +}