fs: update + consolidate path normalization logic

This commit is contained in:
Michael Scire 2020-12-06 19:56:45 -08:00
parent 5ef93778f6
commit 32803d9920
22 changed files with 1007 additions and 463 deletions

View file

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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
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<char[], fs::impl::Deleter> replacement_path;
if (IsParentDirectoryPathReplacementNeeded(path)) {
/* Allocate a buffer to hold the replacement path. */
replacement_path = fs::impl::MakeUnique<char[]>(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();
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
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;
}
}
}
}

View file

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

View file

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