| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240 |
- // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
- // SPDX-License-Identifier: GPL-2.0-or-later
- #pragma once
- #include "common/assert.h"
- #include "common/common_funcs.h"
- #include "common/common_types.h"
- #include "common/scope_exit.h"
- #include "core/file_sys/fs_directory.h"
- #include "core/file_sys/fs_memory_management.h"
- #include "core/file_sys/fs_string_util.h"
- #include "core/hle/result.h"
- namespace FileSys {
- constexpr inline size_t MountNameLengthMax = 15;
- namespace StringTraits {
- constexpr inline char DirectorySeparator = '/';
- constexpr inline char DriveSeparator = ':';
- constexpr inline char Dot = '.';
- constexpr inline char NullTerminator = '\x00';
- constexpr inline char AlternateDirectorySeparator = '\\';
- constexpr inline const char InvalidCharacters[6] = {':', '*', '?', '<', '>', '|'};
- constexpr inline const char InvalidCharactersForHostName[6] = {':', '*', '<', '>', '|', '$'};
- constexpr inline const char InvalidCharactersForMountName[5] = {'*', '?', '<', '>', '|'};
- namespace impl {
- template <const char* InvalidCharacterSet, size_t NumInvalidCharacters>
- consteval u64 MakeInvalidCharacterMask(size_t n) {
- u64 mask = 0;
- for (size_t i = 0; i < NumInvalidCharacters; ++i) {
- if ((static_cast<u64>(InvalidCharacterSet[i]) >> 6) == n) {
- mask |= static_cast<u64>(1) << (static_cast<u64>(InvalidCharacterSet[i]) & 0x3F);
- }
- }
- return mask;
- }
- template <const char* InvalidCharacterSet, size_t NumInvalidCharacters>
- constexpr bool IsInvalidCharacterImpl(char c) {
- constexpr u64 Masks[4] = {
- MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(0),
- MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(1),
- MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(2),
- MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(3)};
- return (Masks[static_cast<u64>(c) >> 6] &
- (static_cast<u64>(1) << (static_cast<u64>(c) & 0x3F))) != 0;
- }
- } // namespace impl
- constexpr bool IsInvalidCharacter(char c) {
- return impl::IsInvalidCharacterImpl<InvalidCharacters, Common::Size(InvalidCharacters)>(c);
- }
- constexpr bool IsInvalidCharacterForHostName(char c) {
- return impl::IsInvalidCharacterImpl<InvalidCharactersForHostName,
- Common::Size(InvalidCharactersForHostName)>(c);
- }
- constexpr bool IsInvalidCharacterForMountName(char c) {
- return impl::IsInvalidCharacterImpl<InvalidCharactersForMountName,
- Common::Size(InvalidCharactersForMountName)>(c);
- }
- } // namespace StringTraits
- constexpr inline size_t WindowsDriveLength = 2;
- constexpr inline size_t UncPathPrefixLength = 2;
- constexpr inline size_t DosDevicePathPrefixLength = 4;
- class PathFlags {
- private:
- static constexpr u32 WindowsPathFlag = (1 << 0);
- static constexpr u32 RelativePathFlag = (1 << 1);
- static constexpr u32 EmptyPathFlag = (1 << 2);
- static constexpr u32 MountNameFlag = (1 << 3);
- static constexpr u32 BackslashFlag = (1 << 4);
- static constexpr u32 AllCharactersFlag = (1 << 5);
- private:
- u32 m_value;
- public:
- constexpr PathFlags() : m_value(0) { /* ... */
- }
- #define DECLARE_PATH_FLAG_HANDLER(__WHICH__) \
- constexpr bool Is##__WHICH__##Allowed() const { return (m_value & __WHICH__##Flag) != 0; } \
- constexpr void Allow##__WHICH__() { m_value |= __WHICH__##Flag; }
- DECLARE_PATH_FLAG_HANDLER(WindowsPath)
- DECLARE_PATH_FLAG_HANDLER(RelativePath)
- DECLARE_PATH_FLAG_HANDLER(EmptyPath)
- DECLARE_PATH_FLAG_HANDLER(MountName)
- DECLARE_PATH_FLAG_HANDLER(Backslash)
- DECLARE_PATH_FLAG_HANDLER(AllCharacters)
- #undef DECLARE_PATH_FLAG_HANDLER
- };
- template <typename T>
- requires(std::same_as<T, char> || std::same_as<T, wchar_t>)
- constexpr inline bool IsDosDevicePath(const T* path) {
- ASSERT(path != nullptr);
- using namespace StringTraits;
- return path[0] == AlternateDirectorySeparator && path[1] == AlternateDirectorySeparator &&
- (path[2] == Dot || path[2] == '?') &&
- (path[3] == DirectorySeparator || path[3] == AlternateDirectorySeparator);
- }
- template <typename T>
- requires(std::same_as<T, char> || std::same_as<T, wchar_t>)
- constexpr inline bool IsUncPath(const T* path, bool allow_forward_slash = true,
- bool allow_back_slash = true) {
- ASSERT(path != nullptr);
- using namespace StringTraits;
- return (allow_forward_slash && path[0] == DirectorySeparator &&
- path[1] == DirectorySeparator) ||
- (allow_back_slash && path[0] == AlternateDirectorySeparator &&
- path[1] == AlternateDirectorySeparator);
- }
- constexpr inline bool IsWindowsDrive(const char* path) {
- ASSERT(path != nullptr);
- return (('a' <= path[0] && path[0] <= 'z') || ('A' <= path[0] && path[0] <= 'Z')) &&
- path[1] == StringTraits::DriveSeparator;
- }
- constexpr inline bool IsWindowsPath(const char* path, bool allow_forward_slash_unc) {
- return IsWindowsDrive(path) || IsDosDevicePath(path) ||
- IsUncPath(path, allow_forward_slash_unc, true);
- }
- constexpr inline int GetWindowsSkipLength(const char* path) {
- if (IsDosDevicePath(path)) {
- return DosDevicePathPrefixLength;
- } else if (IsWindowsDrive(path)) {
- return WindowsDriveLength;
- } else if (IsUncPath(path)) {
- return UncPathPrefixLength;
- } else {
- return 0;
- }
- }
- constexpr inline bool IsPathAbsolute(const char* path) {
- return IsWindowsPath(path, false) || path[0] == StringTraits::DirectorySeparator;
- }
- constexpr inline bool IsPathRelative(const char* path) {
- return path[0] && !IsPathAbsolute(path);
- }
- constexpr inline bool IsCurrentDirectory(const char* path) {
- return path[0] == StringTraits::Dot &&
- (path[1] == StringTraits::NullTerminator || path[1] == StringTraits::DirectorySeparator);
- }
- constexpr inline bool IsParentDirectory(const char* path) {
- return path[0] == StringTraits::Dot && path[1] == StringTraits::Dot &&
- (path[2] == StringTraits::NullTerminator || path[2] == StringTraits::DirectorySeparator);
- }
- constexpr inline bool IsPathStartWithCurrentDirectory(const char* path) {
- return IsCurrentDirectory(path) || IsParentDirectory(path);
- }
- constexpr inline bool IsSubPath(const char* lhs, const char* rhs) {
- // Check pre-conditions
- ASSERT(lhs != nullptr);
- ASSERT(rhs != nullptr);
- // Import StringTraits names for current scope
- using namespace StringTraits;
- // Special case certain paths
- if (IsUncPath(lhs) && !IsUncPath(rhs)) {
- return false;
- }
- if (!IsUncPath(lhs) && IsUncPath(rhs)) {
- return false;
- }
- if (lhs[0] == DirectorySeparator && lhs[1] == NullTerminator && rhs[0] == DirectorySeparator &&
- rhs[1] != NullTerminator) {
- return true;
- }
- if (rhs[0] == DirectorySeparator && rhs[1] == NullTerminator && lhs[0] == DirectorySeparator &&
- lhs[1] != NullTerminator) {
- return true;
- }
- // Check subpath
- for (size_t i = 0; /* ... */; ++i) {
- if (lhs[i] == NullTerminator) {
- return rhs[i] == DirectorySeparator;
- } else if (rhs[i] == NullTerminator) {
- return lhs[i] == DirectorySeparator;
- } else if (lhs[i] != rhs[i]) {
- return false;
- }
- }
- }
- // Path utilities
- constexpr inline void Replace(char* dst, size_t dst_size, char old_char, char new_char) {
- ASSERT(dst != nullptr);
- for (char* cur = dst; cur < dst + dst_size && *cur; ++cur) {
- if (*cur == old_char) {
- *cur = new_char;
- }
- }
- }
- constexpr inline Result CheckUtf8(const char* s) {
- // Check pre-conditions
- ASSERT(s != nullptr);
- // Iterate, checking for utf8-validity
- while (*s) {
- char utf8_buf[4] = {};
- const auto pick_res = PickOutCharacterFromUtf8String(utf8_buf, std::addressof(s));
- R_UNLESS(pick_res == CharacterEncodingResult_Success, ResultInvalidPathFormat);
- u32 dummy;
- const auto cvt_res = ConvertCharacterUtf8ToUtf32(std::addressof(dummy), utf8_buf);
- R_UNLESS(cvt_res == CharacterEncodingResult_Success, ResultInvalidPathFormat);
- }
- R_SUCCEED();
- }
- // Path formatting
- class PathNormalizer {
- private:
- enum class PathState {
- Start,
- Normal,
- FirstSeparator,
- Separator,
- CurrentDir,
- ParentDir,
- };
- private:
- static constexpr void ReplaceParentDirectoryPath(char* dst, const char* src) {
- // Use StringTraits names for remainder of scope
- using namespace StringTraits;
- // Start with a dir-separator
- dst[0] = DirectorySeparator;
- auto i = 1;
- while (src[i] != NullTerminator) {
- if ((src[i - 1] == DirectorySeparator || src[i - 1] == AlternateDirectorySeparator) &&
- src[i + 0] == Dot && src[i + 1] == Dot &&
- (src[i + 2] == DirectorySeparator || src[i + 2] == AlternateDirectorySeparator)) {
- dst[i - 1] = DirectorySeparator;
- dst[i + 0] = Dot;
- dst[i + 1] = Dot;
- dst[i + 2] = DirectorySeparator;
- i += 3;
- } else {
- if (src[i - 1] == AlternateDirectorySeparator && src[i + 0] == Dot &&
- src[i + 1] == Dot && src[i + 2] == NullTerminator) {
- dst[i - 1] = DirectorySeparator;
- dst[i + 0] = Dot;
- dst[i + 1] = Dot;
- i += 2;
- break;
- }
- dst[i] = src[i];
- ++i;
- }
- }
- dst[i] = StringTraits::NullTerminator;
- }
- public:
- static constexpr bool IsParentDirectoryPathReplacementNeeded(const char* path) {
- // Use StringTraits names for remainder of scope
- using namespace StringTraits;
- if (path[0] != DirectorySeparator && path[0] != AlternateDirectorySeparator) {
- return false;
- }
- // Check to find a parent reference using alternate separators
- if (path[0] != NullTerminator && path[1] != NullTerminator && path[2] != NullTerminator) {
- size_t i;
- for (i = 0; path[i + 3] != NullTerminator; ++path) {
- if (path[i + 1] != Dot || path[i + 2] != Dot) {
- continue;
- }
- const char c0 = path[i + 0];
- const char c3 = path[i + 3];
- if (c0 == AlternateDirectorySeparator &&
- (c3 == DirectorySeparator || c3 == AlternateDirectorySeparator ||
- c3 == NullTerminator)) {
- return true;
- }
- if (c3 == AlternateDirectorySeparator &&
- (c0 == DirectorySeparator || c0 == AlternateDirectorySeparator)) {
- return true;
- }
- }
- if (path[i + 0] == AlternateDirectorySeparator && path[i + 1] == Dot &&
- path[i + 2] == Dot /* && path[i + 3] == NullTerminator */) {
- return true;
- }
- }
- return false;
- }
- static constexpr Result IsNormalized(bool* out, size_t* out_len, const char* path,
- bool allow_all_characters = false) {
- // Use StringTraits names for remainder of scope
- using namespace StringTraits;
- // Parse the path
- auto state = PathState::Start;
- size_t len = 0;
- while (path[len] != NullTerminator) {
- // Get the current character
- const char c = path[len++];
- // Check the current character is valid
- if (!allow_all_characters && state != PathState::Start) {
- R_UNLESS(!IsInvalidCharacter(c), ResultInvalidCharacter);
- }
- // Process depending on current state
- switch (state) {
- // Import the PathState enums for convenience
- using enum PathState;
- case Start:
- R_UNLESS(c == DirectorySeparator, ResultInvalidPathFormat);
- state = FirstSeparator;
- break;
- case Normal:
- if (c == DirectorySeparator) {
- state = Separator;
- }
- break;
- case FirstSeparator:
- case Separator:
- if (c == DirectorySeparator) {
- *out = false;
- R_SUCCEED();
- }
- if (c == Dot) {
- state = CurrentDir;
- } else {
- state = Normal;
- }
- break;
- case CurrentDir:
- if (c == DirectorySeparator) {
- *out = false;
- R_SUCCEED();
- }
- if (c == Dot) {
- state = ParentDir;
- } else {
- state = Normal;
- }
- break;
- case ParentDir:
- if (c == DirectorySeparator) {
- *out = false;
- R_SUCCEED();
- }
- state = Normal;
- break;
- default:
- UNREACHABLE();
- break;
- }
- }
- // Check the final state
- switch (state) {
- // Import the PathState enums for convenience
- using enum PathState;
- case Start:
- R_THROW(ResultInvalidPathFormat);
- case Normal:
- case FirstSeparator:
- *out = true;
- break;
- case Separator:
- case CurrentDir:
- case ParentDir:
- *out = false;
- break;
- default:
- UNREACHABLE();
- break;
- }
- // Set the output length
- *out_len = len;
- R_SUCCEED();
- }
- static constexpr Result Normalize(char* dst, size_t* out_len, const char* path,
- size_t max_out_size, bool is_windows_path,
- bool is_drive_relative_path,
- bool allow_all_characters = false) {
- // Use StringTraits names for remainder of scope
- using namespace StringTraits;
- // Prepare to iterate
- const char* cur_path = path;
- size_t total_len = 0;
- // If path begins with a separator, check that we're not drive relative
- if (cur_path[0] != DirectorySeparator) {
- R_UNLESS(is_drive_relative_path, ResultInvalidPathFormat);
- dst[total_len++] = DirectorySeparator;
- }
- // We're going to need to do path replacement, potentially
- char* replacement_path = nullptr;
- size_t replacement_path_size = 0;
- SCOPE_EXIT {
- if (replacement_path != nullptr) {
- if (std::is_constant_evaluated()) {
- delete[] replacement_path;
- } else {
- Deallocate(replacement_path, replacement_path_size);
- }
- }
- };
- // Perform path replacement, if necessary
- if (IsParentDirectoryPathReplacementNeeded(cur_path)) {
- if (std::is_constant_evaluated()) {
- replacement_path_size = EntryNameLengthMax + 1;
- replacement_path = new char[replacement_path_size];
- } else {
- replacement_path_size = EntryNameLengthMax + 1;
- replacement_path = static_cast<char*>(Allocate(replacement_path_size));
- }
- ReplaceParentDirectoryPath(replacement_path, cur_path);
- cur_path = replacement_path;
- }
- // Iterate, normalizing path components
- bool skip_next_sep = false;
- size_t i = 0;
- while (cur_path[i] != NullTerminator) {
- // Process a directory separator, if we run into one
- if (cur_path[i] == DirectorySeparator) {
- // Swallow separators
- do {
- ++i;
- } while (cur_path[i] == DirectorySeparator);
- // Check if we hit end of string
- if (cur_path[i] == NullTerminator) {
- break;
- }
- // If we aren't skipping the separator, write it, checking that we remain in bounds.
- if (!skip_next_sep) {
- if (total_len + 1 == max_out_size) {
- dst[total_len] = NullTerminator;
- *out_len = total_len;
- R_THROW(ResultTooLongPath);
- }
- dst[total_len++] = DirectorySeparator;
- }
- // Don't skip the next separator
- skip_next_sep = false;
- }
- // Get the length of the current directory component
- size_t dir_len = 0;
- while (cur_path[i + dir_len] != DirectorySeparator &&
- cur_path[i + dir_len] != NullTerminator) {
- // Check for validity
- if (!allow_all_characters) {
- R_UNLESS(!IsInvalidCharacter(cur_path[i + dir_len]), ResultInvalidCharacter);
- }
- ++dir_len;
- }
- // Handle the current dir component
- if (IsCurrentDirectory(cur_path + i)) {
- skip_next_sep = true;
- } else if (IsParentDirectory(cur_path + i)) {
- // We should have just written a separator
- ASSERT(dst[total_len - 1] == DirectorySeparator);
- // We should have started with a separator, for non-windows paths
- if (!is_windows_path) {
- ASSERT(dst[0] == DirectorySeparator);
- }
- // Remove the previous component
- if (total_len == 1) {
- R_UNLESS(is_windows_path, ResultDirectoryUnobtainable);
- --total_len;
- } else {
- total_len -= 2;
- do {
- if (dst[total_len] == DirectorySeparator) {
- break;
- }
- } while ((--total_len) != 0);
- }
- // We should be pointing to a directory separator, for non-windows paths
- if (!is_windows_path) {
- ASSERT(dst[total_len] == DirectorySeparator);
- }
- // We should remain in bounds
- ASSERT(total_len < max_out_size);
- } else {
- // Copy, possibly truncating
- if (total_len + dir_len + 1 > max_out_size) {
- const size_t copy_len = max_out_size - (total_len + 1);
- for (size_t j = 0; j < copy_len; ++j) {
- dst[total_len++] = cur_path[i + j];
- }
- dst[total_len] = NullTerminator;
- *out_len = total_len;
- R_THROW(ResultTooLongPath);
- }
- for (size_t j = 0; j < dir_len; ++j) {
- dst[total_len++] = cur_path[i + j];
- }
- }
- // Advance past the current directory component
- i += dir_len;
- }
- if (skip_next_sep) {
- --total_len;
- }
- if (total_len == 0 && max_out_size != 0) {
- total_len = 1;
- dst[0] = DirectorySeparator;
- }
- // NOTE: Probable nintendo bug, as max_out_size must be at least total_len + 1 for the null
- // terminator.
- R_UNLESS(max_out_size >= total_len - 1, ResultTooLongPath);
- dst[total_len] = NullTerminator;
- // Check that the result path is normalized
- bool is_normalized;
- size_t dummy;
- R_TRY(IsNormalized(std::addressof(is_normalized), std::addressof(dummy), dst,
- allow_all_characters));
- // Assert that the result path is normalized
- ASSERT(is_normalized);
- // Set the output length
- *out_len = total_len;
- R_SUCCEED();
- }
- };
- class PathFormatter {
- private:
- static constexpr Result CheckSharedName(const char* name, size_t len) {
- // Use StringTraits names for remainder of scope
- using namespace StringTraits;
- if (len == 1) {
- R_UNLESS(name[0] != Dot, ResultInvalidPathFormat);
- } else if (len == 2) {
- R_UNLESS(name[0] != Dot || name[1] != Dot, ResultInvalidPathFormat);
- }
- for (size_t i = 0; i < len; ++i) {
- R_UNLESS(!IsInvalidCharacter(name[i]), ResultInvalidCharacter);
- }
- R_SUCCEED();
- }
- static constexpr Result CheckHostName(const char* name, size_t len) {
- // Use StringTraits names for remainder of scope
- using namespace StringTraits;
- if (len == 2) {
- R_UNLESS(name[0] != Dot || name[1] != Dot, ResultInvalidPathFormat);
- }
- for (size_t i = 0; i < len; ++i) {
- R_UNLESS(!IsInvalidCharacterForHostName(name[i]), ResultInvalidCharacter);
- }
- R_SUCCEED();
- }
- static constexpr Result CheckInvalidBackslash(bool* out_contains_backslash, const char* path,
- bool allow_backslash) {
- // Use StringTraits names for remainder of scope
- using namespace StringTraits;
- // Default to no backslashes, so we can just write if we see one
- *out_contains_backslash = false;
- while (*path != NullTerminator) {
- if (*(path++) == AlternateDirectorySeparator) {
- *out_contains_backslash = true;
- R_UNLESS(allow_backslash, ResultInvalidCharacter);
- }
- }
- R_SUCCEED();
- }
- public:
- static constexpr Result CheckPathFormat(const char* path, const PathFlags& flags) {
- bool normalized;
- size_t len;
- R_RETURN(IsNormalized(std::addressof(normalized), std::addressof(len), path, flags));
- }
- static constexpr Result SkipMountName(const char** out, size_t* out_len, const char* path) {
- R_RETURN(ParseMountName(out, out_len, nullptr, 0, path));
- }
- static constexpr Result ParseMountName(const char** out, size_t* out_len, char* out_mount_name,
- size_t out_mount_name_buffer_size, const char* path) {
- // Check pre-conditions
- ASSERT(path != nullptr);
- ASSERT(out_len != nullptr);
- ASSERT(out != nullptr);
- ASSERT((out_mount_name == nullptr) == (out_mount_name_buffer_size == 0));
- // Use StringTraits names for remainder of scope
- using namespace StringTraits;
- // Determine max mount length
- const auto max_mount_len =
- out_mount_name_buffer_size == 0
- ? MountNameLengthMax + 1
- : std::min(MountNameLengthMax + 1, out_mount_name_buffer_size);
- // Parse the path until we see a drive separator
- size_t mount_len = 0;
- for (/* ... */; mount_len < max_mount_len && path[mount_len]; ++mount_len) {
- const char c = path[mount_len];
- // If we see a drive separator, advance, then we're done with the pre-drive separator
- // part of the mount.
- if (c == DriveSeparator) {
- ++mount_len;
- break;
- }
- // If we see a directory separator, we're not in a mount name
- if (c == DirectorySeparator || c == AlternateDirectorySeparator) {
- *out = path;
- *out_len = 0;
- R_SUCCEED();
- }
- }
- // Check to be sure we're actually looking at a mount name
- if (mount_len <= 2 || path[mount_len - 1] != DriveSeparator) {
- *out = path;
- *out_len = 0;
- R_SUCCEED();
- }
- // Check that all characters in the mount name are allowable
- for (size_t i = 0; i < mount_len; ++i) {
- R_UNLESS(!IsInvalidCharacterForMountName(path[i]), ResultInvalidCharacter);
- }
- // Copy out the mount name
- if (out_mount_name_buffer_size > 0) {
- R_UNLESS(mount_len < out_mount_name_buffer_size, ResultTooLongPath);
- for (size_t i = 0; i < mount_len; ++i) {
- out_mount_name[i] = path[i];
- }
- out_mount_name[mount_len] = NullTerminator;
- }
- // Set the output
- *out = path + mount_len;
- *out_len = mount_len;
- R_SUCCEED();
- }
- static constexpr Result SkipRelativeDotPath(const char** out, size_t* out_len,
- const char* path) {
- R_RETURN(ParseRelativeDotPath(out, out_len, nullptr, 0, path));
- }
- static constexpr Result ParseRelativeDotPath(const char** out, size_t* out_len,
- char* out_relative,
- size_t out_relative_buffer_size,
- const char* path) {
- // Check pre-conditions
- ASSERT(path != nullptr);
- ASSERT(out_len != nullptr);
- ASSERT(out != nullptr);
- ASSERT((out_relative == nullptr) == (out_relative_buffer_size == 0));
- // Use StringTraits names for remainder of scope
- using namespace StringTraits;
- // Initialize the output buffer, if we have one
- if (out_relative_buffer_size > 0) {
- out_relative[0] = NullTerminator;
- }
- // Check if the path is relative
- if (path[0] == Dot && (path[1] == NullTerminator || path[1] == DirectorySeparator ||
- path[1] == AlternateDirectorySeparator)) {
- if (out_relative_buffer_size > 0) {
- R_UNLESS(out_relative_buffer_size >= 2, ResultTooLongPath);
- out_relative[0] = Dot;
- out_relative[1] = NullTerminator;
- }
- *out = path + 1;
- *out_len = 1;
- R_SUCCEED();
- }
- // Ensure the path isn't a parent directory
- R_UNLESS(!(path[0] == Dot && path[1] == Dot), ResultDirectoryUnobtainable);
- // There was no relative dot path
- *out = path;
- *out_len = 0;
- R_SUCCEED();
- }
- static constexpr Result SkipWindowsPath(const char** out, size_t* out_len, bool* out_normalized,
- const char* path, bool has_mount_name) {
- // We're normalized if and only if the parsing doesn't throw ResultNotNormalized()
- *out_normalized = true;
- R_TRY_CATCH(ParseWindowsPath(out, out_len, nullptr, 0, path, has_mount_name)) {
- R_CATCH(ResultNotNormalized) {
- *out_normalized = false;
- }
- }
- R_END_TRY_CATCH;
- ON_RESULT_INCLUDED(ResultNotNormalized) {
- *out_normalized = false;
- };
- R_SUCCEED();
- }
- static constexpr Result ParseWindowsPath(const char** out, size_t* out_len, char* out_win,
- size_t out_win_buffer_size, const char* path,
- bool has_mount_name) {
- // Check pre-conditions
- ASSERT(path != nullptr);
- ASSERT(out_len != nullptr);
- ASSERT(out != nullptr);
- ASSERT((out_win == nullptr) == (out_win_buffer_size == 0));
- // Use StringTraits names for remainder of scope
- using namespace StringTraits;
- // Initialize the output buffer, if we have one
- if (out_win_buffer_size > 0) {
- out_win[0] = NullTerminator;
- }
- // Handle path start
- const char* cur_path = path;
- if (has_mount_name && path[0] == DirectorySeparator) {
- if (path[1] == AlternateDirectorySeparator && path[2] == AlternateDirectorySeparator) {
- R_UNLESS(out_win_buffer_size > 0, ResultNotNormalized);
- ++cur_path;
- } else if (IsWindowsDrive(path + 1)) {
- R_UNLESS(out_win_buffer_size > 0, ResultNotNormalized);
- ++cur_path;
- }
- }
- // Handle windows drive
- if (IsWindowsDrive(cur_path)) {
- // Parse up to separator
- size_t win_path_len = WindowsDriveLength;
- for (/* ... */; cur_path[win_path_len] != NullTerminator; ++win_path_len) {
- R_UNLESS(!IsInvalidCharacter(cur_path[win_path_len]), ResultInvalidCharacter);
- if (cur_path[win_path_len] == DirectorySeparator ||
- cur_path[win_path_len] == AlternateDirectorySeparator) {
- break;
- }
- }
- // Ensure that we're normalized, if we're required to be
- if (out_win_buffer_size == 0) {
- for (size_t i = 0; i < win_path_len; ++i) {
- R_UNLESS(cur_path[i] != AlternateDirectorySeparator, ResultNotNormalized);
- }
- } else {
- // Ensure we can copy into the normalized buffer
- R_UNLESS(win_path_len < out_win_buffer_size, ResultTooLongPath);
- for (size_t i = 0; i < win_path_len; ++i) {
- out_win[i] = cur_path[i];
- }
- out_win[win_path_len] = NullTerminator;
- Replace(out_win, win_path_len, AlternateDirectorySeparator, DirectorySeparator);
- }
- *out = cur_path + win_path_len;
- *out_len = win_path_len;
- R_SUCCEED();
- }
- // Handle DOS device
- if (IsDosDevicePath(cur_path)) {
- size_t dos_prefix_len = DosDevicePathPrefixLength;
- if (IsWindowsDrive(cur_path + dos_prefix_len)) {
- dos_prefix_len += WindowsDriveLength;
- } else {
- --dos_prefix_len;
- }
- if (out_win_buffer_size > 0) {
- // Ensure we can copy into the normalized buffer
- R_UNLESS(dos_prefix_len < out_win_buffer_size, ResultTooLongPath);
- for (size_t i = 0; i < dos_prefix_len; ++i) {
- out_win[i] = cur_path[i];
- }
- out_win[dos_prefix_len] = NullTerminator;
- Replace(out_win, dos_prefix_len, DirectorySeparator, AlternateDirectorySeparator);
- }
- *out = cur_path + dos_prefix_len;
- *out_len = dos_prefix_len;
- R_SUCCEED();
- }
- // Handle UNC path
- if (IsUncPath(cur_path, false, true)) {
- const char* final_path = cur_path;
- R_UNLESS(cur_path[UncPathPrefixLength] != DirectorySeparator, ResultInvalidPathFormat);
- R_UNLESS(cur_path[UncPathPrefixLength] != AlternateDirectorySeparator,
- ResultInvalidPathFormat);
- size_t cur_component_offset = 0;
- size_t pos = UncPathPrefixLength;
- for (/* ... */; cur_path[pos] != NullTerminator; ++pos) {
- if (cur_path[pos] == DirectorySeparator ||
- cur_path[pos] == AlternateDirectorySeparator) {
- if (cur_component_offset != 0) {
- R_TRY(CheckSharedName(cur_path + cur_component_offset,
- pos - cur_component_offset));
- final_path = cur_path + pos;
- break;
- }
- R_UNLESS(cur_path[pos + 1] != DirectorySeparator, ResultInvalidPathFormat);
- R_UNLESS(cur_path[pos + 1] != AlternateDirectorySeparator,
- ResultInvalidPathFormat);
- R_TRY(CheckHostName(cur_path + 2, pos - 2));
- cur_component_offset = pos + 1;
- }
- }
- R_UNLESS(cur_component_offset != pos, ResultInvalidPathFormat);
- if (cur_component_offset != 0 && final_path == cur_path) {
- R_TRY(CheckSharedName(cur_path + cur_component_offset, pos - cur_component_offset));
- final_path = cur_path + pos;
- }
- size_t unc_prefix_len = final_path - cur_path;
- // Ensure that we're normalized, if we're required to be
- if (out_win_buffer_size == 0) {
- for (size_t i = 0; i < unc_prefix_len; ++i) {
- R_UNLESS(cur_path[i] != DirectorySeparator, ResultNotNormalized);
- }
- } else {
- // Ensure we can copy into the normalized buffer
- R_UNLESS(unc_prefix_len < out_win_buffer_size, ResultTooLongPath);
- for (size_t i = 0; i < unc_prefix_len; ++i) {
- out_win[i] = cur_path[i];
- }
- out_win[unc_prefix_len] = NullTerminator;
- Replace(out_win, unc_prefix_len, DirectorySeparator, AlternateDirectorySeparator);
- }
- *out = cur_path + unc_prefix_len;
- *out_len = unc_prefix_len;
- R_SUCCEED();
- }
- // There's no windows path to parse
- *out = path;
- *out_len = 0;
- R_SUCCEED();
- }
- static constexpr Result IsNormalized(bool* out, size_t* out_len, const char* path,
- const PathFlags& flags = {}) {
- // Ensure nothing is null
- R_UNLESS(out != nullptr, ResultNullptrArgument);
- R_UNLESS(out_len != nullptr, ResultNullptrArgument);
- R_UNLESS(path != nullptr, ResultNullptrArgument);
- // Verify that the path is valid utf-8
- R_TRY(CheckUtf8(path));
- // Use StringTraits names for remainder of scope
- using namespace StringTraits;
- // Handle the case where the path is empty
- if (path[0] == NullTerminator) {
- R_UNLESS(flags.IsEmptyPathAllowed(), ResultInvalidPathFormat);
- *out = true;
- *out_len = 0;
- R_SUCCEED();
- }
- // All normalized paths start with a directory separator...unless they're windows paths,
- // relative paths, or have mount names.
- if (path[0] != DirectorySeparator) {
- R_UNLESS(flags.IsWindowsPathAllowed() || flags.IsRelativePathAllowed() ||
- flags.IsMountNameAllowed(),
- ResultInvalidPathFormat);
- }
- // Check that the path is allowed to be a windows path, if it is
- if (IsWindowsPath(path, false)) {
- R_UNLESS(flags.IsWindowsPathAllowed(), ResultInvalidPathFormat);
- }
- // Skip past the mount name, if one is present
- size_t total_len = 0;
- size_t mount_name_len = 0;
- R_TRY(SkipMountName(std::addressof(path), std::addressof(mount_name_len), path));
- // If we had a mount name, check that that was allowed
- if (mount_name_len > 0) {
- R_UNLESS(flags.IsMountNameAllowed(), ResultInvalidPathFormat);
- total_len += mount_name_len;
- }
- // Check that the path starts as a normalized path should
- if (path[0] != DirectorySeparator && !IsPathStartWithCurrentDirectory(path) &&
- !IsWindowsPath(path, false)) {
- R_UNLESS(flags.IsRelativePathAllowed(), ResultInvalidPathFormat);
- R_UNLESS(!IsInvalidCharacter(path[0]), ResultInvalidPathFormat);
- *out = false;
- R_SUCCEED();
- }
- // Process relative path
- size_t relative_len = 0;
- R_TRY(SkipRelativeDotPath(std::addressof(path), std::addressof(relative_len), path));
- // If we have a relative path, check that was allowed
- if (relative_len > 0) {
- R_UNLESS(flags.IsRelativePathAllowed(), ResultInvalidPathFormat);
- total_len += relative_len;
- if (path[0] == NullTerminator) {
- *out = true;
- *out_len = total_len;
- R_SUCCEED();
- }
- }
- // Process windows path
- size_t windows_len = 0;
- bool normalized_win = false;
- R_TRY(SkipWindowsPath(std::addressof(path), std::addressof(windows_len),
- std::addressof(normalized_win), path, mount_name_len > 0));
- // If the windows path wasn't normalized, we're not normalized
- if (!normalized_win) {
- R_UNLESS(flags.IsWindowsPathAllowed(), ResultInvalidPathFormat);
- *out = false;
- R_SUCCEED();
- }
- // If we had a windows path, check that was allowed
- if (windows_len > 0) {
- R_UNLESS(flags.IsWindowsPathAllowed(), ResultInvalidPathFormat);
- total_len += windows_len;
- // We can't have both a relative path and a windows path
- R_UNLESS(relative_len == 0, ResultInvalidPathFormat);
- // A path ending in a windows path isn't normalized
- if (path[0] == NullTerminator) {
- *out = false;
- R_SUCCEED();
- }
- // Check that there are no windows directory separators in the path
- for (size_t i = 0; path[i] != NullTerminator; ++i) {
- if (path[i] == AlternateDirectorySeparator) {
- *out = false;
- R_SUCCEED();
- }
- }
- }
- // Check that parent directory replacement is not needed if backslashes are allowed
- if (flags.IsBackslashAllowed() &&
- PathNormalizer::IsParentDirectoryPathReplacementNeeded(path)) {
- *out = false;
- R_SUCCEED();
- }
- // Check that the backslash state is valid
- bool is_backslash_contained = false;
- R_TRY(CheckInvalidBackslash(std::addressof(is_backslash_contained), path,
- flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed()));
- // Check that backslashes are contained only if allowed
- if (is_backslash_contained && !flags.IsBackslashAllowed()) {
- *out = false;
- R_SUCCEED();
- }
- // Check that the final result path is normalized
- size_t normal_len = 0;
- R_TRY(PathNormalizer::IsNormalized(out, std::addressof(normal_len), path,
- flags.IsAllCharactersAllowed()));
- // Add the normal length
- total_len += normal_len;
- // Set the output length
- *out_len = total_len;
- R_SUCCEED();
- }
- static constexpr Result Normalize(char* dst, size_t dst_size, const char* path, size_t path_len,
- const PathFlags& flags) {
- // Use StringTraits names for remainder of scope
- using namespace StringTraits;
- // Prepare to iterate
- const char* src = path;
- size_t cur_pos = 0;
- bool is_windows_path = false;
- // Check if the path is empty
- if (src[0] == NullTerminator) {
- if (dst_size != 0) {
- dst[0] = NullTerminator;
- }
- R_UNLESS(flags.IsEmptyPathAllowed(), ResultInvalidPathFormat);
- R_SUCCEED();
- }
- // Handle a mount name
- size_t mount_name_len = 0;
- if (flags.IsMountNameAllowed()) {
- R_TRY(ParseMountName(std::addressof(src), std::addressof(mount_name_len), dst + cur_pos,
- dst_size - cur_pos, src));
- cur_pos += mount_name_len;
- }
- // Handle a drive-relative prefix
- bool is_drive_relative = false;
- if (src[0] != DirectorySeparator && !IsPathStartWithCurrentDirectory(src) &&
- !IsWindowsPath(src, false)) {
- R_UNLESS(flags.IsRelativePathAllowed(), ResultInvalidPathFormat);
- R_UNLESS(!IsInvalidCharacter(src[0]), ResultInvalidPathFormat);
- dst[cur_pos++] = Dot;
- is_drive_relative = true;
- }
- size_t relative_len = 0;
- if (flags.IsRelativePathAllowed()) {
- R_UNLESS(cur_pos < dst_size, ResultTooLongPath);
- R_TRY(ParseRelativeDotPath(std::addressof(src), std::addressof(relative_len),
- dst + cur_pos, dst_size - cur_pos, src));
- cur_pos += relative_len;
- if (src[0] == NullTerminator) {
- R_UNLESS(cur_pos < dst_size, ResultTooLongPath);
- dst[cur_pos] = NullTerminator;
- R_SUCCEED();
- }
- }
- // Handle a windows path
- if (flags.IsWindowsPathAllowed()) {
- const char* const orig = src;
- R_UNLESS(cur_pos < dst_size, ResultTooLongPath);
- size_t windows_len = 0;
- R_TRY(ParseWindowsPath(std::addressof(src), std::addressof(windows_len), dst + cur_pos,
- dst_size - cur_pos, src, mount_name_len != 0));
- cur_pos += windows_len;
- if (src[0] == NullTerminator) {
- /* NOTE: Bug in original code here repeated, should be checking cur_pos + 2. */
- R_UNLESS(cur_pos + 1 < dst_size, ResultTooLongPath);
- dst[cur_pos + 0] = DirectorySeparator;
- dst[cur_pos + 1] = NullTerminator;
- R_SUCCEED();
- }
- if ((src - orig) > 0) {
- is_windows_path = true;
- }
- }
- // Check for invalid backslash
- bool backslash_contained = false;
- R_TRY(CheckInvalidBackslash(std::addressof(backslash_contained), src,
- flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed()));
- // Handle backslash replacement as necessary
- if (backslash_contained && flags.IsWindowsPathAllowed()) {
- // Create a temporary buffer holding a slash-replaced version of the path.
- // NOTE: Nintendo unnecessarily allocates and replaces here a fully copy of the path,
- // despite having skipped some of it already.
- const size_t replaced_src_len = path_len - (src - path);
- char* replaced_src = nullptr;
- SCOPE_EXIT {
- if (replaced_src != nullptr) {
- if (std::is_constant_evaluated()) {
- delete[] replaced_src;
- } else {
- Deallocate(replaced_src, replaced_src_len);
- }
- }
- };
- if (std::is_constant_evaluated()) {
- replaced_src = new char[replaced_src_len];
- } else {
- replaced_src = static_cast<char*>(Allocate(replaced_src_len));
- }
- Strlcpy<char>(replaced_src, src, replaced_src_len);
- Replace(replaced_src, replaced_src_len, AlternateDirectorySeparator,
- DirectorySeparator);
- size_t dummy;
- R_TRY(PathNormalizer::Normalize(dst + cur_pos, std::addressof(dummy), replaced_src,
- dst_size - cur_pos, is_windows_path, is_drive_relative,
- flags.IsAllCharactersAllowed()));
- } else {
- // We can just do normalization
- size_t dummy;
- R_TRY(PathNormalizer::Normalize(dst + cur_pos, std::addressof(dummy), src,
- dst_size - cur_pos, is_windows_path, is_drive_relative,
- flags.IsAllCharactersAllowed()));
- }
- R_SUCCEED();
- }
- };
- } // namespace FileSys
|