/* * Copyright (c) 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 { constexpr size_t DefaultPathBufferSize = fs::EntryNameLengthMax + 1; //#define ENABLE_PRINT_FORMAT_TEST_DEBUGGING #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) template struct Print { constexpr Print() { if (std::is_constant_evaluated()) { __builtin_unreachable(); } } }; template consteval void PrintResultMismatchImpl(u32 e, u32 a) { if constexpr (N == 32) { Print{}; } else { const bool is_e = (e & (1 << N)) != 0; const bool is_a = (a & (1 << N)) != 0; if (is_e) { if (is_a) { PrintResultMismatchImpl(e, a); } else { PrintResultMismatchImpl(e, a); } } else { if (is_a) { PrintResultMismatchImpl(e, a); } else { PrintResultMismatchImpl(e, a); } } } } consteval void PrintResultMismatch(const Result &lhs, const Result &rhs) { PrintResultMismatchImpl(lhs.GetDescription(), rhs.GetDescription()); } template struct PrintMismatchChar { constexpr PrintMismatchChar() { if (std::is_constant_evaluated()) { __builtin_unreachable(); } } }; template consteval void PrintCharacterMismatch(char c) { if (c == static_cast(C)) { PrintMismatchChar(C)>{}; return; } if constexpr (C < std::numeric_limits::max()) { PrintCharacterMismatch(c); } } template consteval void PrintCharacterMismatch(char c, char c2) { if (c == static_cast(C)) { PrintCharacterMismatch(C)>(c2); return; } if constexpr (C < std::numeric_limits::max()) { PrintCharacterMismatch(c, c2); } } template consteval void PrintCharacterMismatch(size_t ix, char c, char c2) { if (Ix == ix) { PrintCharacterMismatch(c, c2); return; } if constexpr (Ix <= DefaultPathBufferSize) { PrintCharacterMismatch(ix, c, c2); } } #endif consteval bool TestNormalizeImpl(const char *path, size_t buffer_size, const char *normalized, bool windows_path, bool drive_relative, bool all_chars, size_t expected_length, Result expected_result) { /* Allocate a buffer to normalize into. */ char *buffer = new char[buffer_size]; ON_SCOPE_EXIT { delete[] buffer; }; buffer[buffer_size - 1] = '\xcc'; /* Perform normalization. */ size_t actual_length; const Result actual_result = PathNormalizer::Normalize(buffer, std::addressof(actual_length), path, buffer_size, windows_path, drive_relative, all_chars); /* Check that the expected result matches the actual. */ if (actual_result.GetValue() != expected_result.GetValue()) { #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) PrintResultMismatch(expected_result.GetValue(), actual_result.GetValue()); #endif return false; } /* Check that the expected string matches the actual. */ for (size_t i = 0; i < buffer_size; ++i) { if (normalized[i] != StringTraits::NullTerminator || R_SUCCEEDED(expected_result)) { if (buffer[i] != normalized[i]) { #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) PrintCharacterMismatch(i, normalized[i], buffer[i]); #endif return false; } } if (normalized[i] == StringTraits::NullTerminator || buffer[i] == StringTraits::NullTerminator) { break; } } /* Check that the expected length matches the actual. */ if (R_SUCCEEDED(expected_result) || fs::ResultTooLongPath::Includes(expected_result)) { if (expected_length != actual_length) { #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) PrintResultMismatchImpl(static_cast(expected_length), static_cast(actual_length)); #endif return false; } } return true; } struct NormalizeTestData { const char *path; bool windows; bool rel; bool allow_all; const char *normalized; size_t len; Result result; }; template consteval bool DoNormalizeTests(const NormalizeTestData (&tests)[N]) { if constexpr (Ix >= N) { return true; } const auto &test = tests[Ix]; if (!TestNormalizeImpl(test.path, DefaultPathBufferSize, test.normalized, test.windows, test.rel, test.allow_all, test.len, test.result)) { return false; } if constexpr (Ix < N) { return DoNormalizeTests(tests); } else { AMS_ASSUME(false); } } consteval bool TestNormalized() { constexpr NormalizeTestData Tests[] = { { "/aa/bb/c/", false, true, false, "/aa/bb/c", 8, ResultSuccess() }, { "aa/bb/c/", false, false, false, "", 0, fs::ResultInvalidPathFormat() }, { "aa/bb/c/", false, true, false, "/aa/bb/c", 8, ResultSuccess() }, { "mount:a/b", false, true, false, "/", 0, fs::ResultInvalidCharacter() }, { "mo|unt:a/b", false, true, true, "/mo|unt:a/b", 11, ResultSuccess() }, { "/aa/bb/../..", true, false, false, "/", 1, ResultSuccess() }, { "/aa/bb/../../..", true, false, false, "/", 1, ResultSuccess() }, { "/aa/bb/../../..", false, false, false, "/aa/bb/", 0, fs::ResultDirectoryUnobtainable() }, { "aa/bb/../../..", true, true, false, "/", 1, ResultSuccess() }, { "aa/bb/../../..", false, true, false, "/aa/bb/", 0, fs::ResultDirectoryUnobtainable() }, { "mount:a/b", false, true, true, "/mount:a/b", 10, ResultSuccess() }, { "/a|/bb/cc", false, false, true, "/a|/bb/cc", 9, ResultSuccess() }, { "/>a/bb/cc", false, false, true, "/>a/bb/cc", 9, ResultSuccess() }, { "/aa/.a/bb/cc", false, false, false, "/", 0, fs::ResultInvalidCharacter() }, { "/aa/. consteval bool DoNormalizeTests(const NormalizeTestDataSmallBuffer (&tests)[N]) { if constexpr (Ix >= N) { return true; } const auto &test = tests[Ix]; if (!TestNormalizeImpl(test.path, test.buffer_size, test.normalized, false, false, false, test.len, test.result)) { return false; } if constexpr (Ix < N) { return DoNormalizeTests(tests); } else { AMS_ASSUME(false); } } consteval bool TestNormalizedSmallBuffer() { constexpr NormalizeTestDataSmallBuffer Tests[] = { { "/aa/bb/cc/", 7, "/aa/bb", 6, fs::ResultTooLongPath() }, { "/aa/bb/cc/", 8, "/aa/bb/", 7, fs::ResultTooLongPath() }, { "/aa/bb/cc/", 9, "/aa/bb/c", 8, fs::ResultTooLongPath() }, { "/aa/bb/cc/", 10, "/aa/bb/cc", 9, ResultSuccess() }, { "/aa/bb/cc", 9, "/aa/bb/c", 8, fs::ResultTooLongPath() }, { "/aa/bb/cc", 10, "/aa/bb/cc", 9, ResultSuccess() }, { "/./aa/./bb/./cc", 9, "/aa/bb/c", 8, fs::ResultTooLongPath() }, { "/./aa/./bb/./cc", 10, "/aa/bb/cc", 9, ResultSuccess() }, { "/aa/bb/cc/../../..", 9, "/aa/bb/c", 8, fs::ResultTooLongPath() }, { "/aa/bb/cc/../../..", 10, "/aa/bb/cc", 9, fs::ResultTooLongPath() }, { "/aa/bb/.", 7, "/aa/bb", 6, fs::ResultTooLongPath() }, { "/aa/bb/./", 7, "/aa/bb", 6, fs::ResultTooLongPath() }, { "/aa/bb/..", 8, "/aa", 3, ResultSuccess() }, { "/aa/bb", 1, "", 0, fs::ResultTooLongPath() }, { "/aa/bb", 2, "/", 1, fs::ResultTooLongPath() }, { "/aa/bb", 3, "/a", 2, fs::ResultTooLongPath() }, { "aa/bb", 1, "", 0, fs::ResultInvalidPathFormat() } }; return DoNormalizeTests(Tests); } static_assert(TestNormalizedSmallBuffer()); consteval bool TestIsNormalizedImpl(const char *path, bool allow_all, bool expected_normalized, size_t expected_size, Result expected_result) { /* Perform normalization checking. */ bool actual_normalized; size_t actual_size = 0; const Result actual_result = PathNormalizer::IsNormalized(std::addressof(actual_normalized), std::addressof(actual_size), path, allow_all); /* Check that the expected result matches the actual. */ if (actual_result.GetValue() != expected_result.GetValue()) { #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) PrintResultMismatch(expected_result.GetValue(), actual_result.GetValue()); #endif return false; } if (expected_size != actual_size) { #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) PrintResultMismatchImpl(static_cast(expected_size), static_cast(actual_size)); #endif return false; } /* Check that the expected output matches the actual. */ if (R_SUCCEEDED(expected_result)) { if (expected_normalized != actual_normalized) { #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) PrintResultMismatchImpl(static_cast(expected_normalized), static_cast(actual_normalized)); #endif return false; } } return true; } struct IsNormalizedTestData { const char *path; bool allow_all; bool normalized; size_t len; Result result; }; template consteval bool DoIsNormalizedTests(const IsNormalizedTestData (&tests)[N]) { if constexpr (Ix >= N) { return true; } const auto &test = tests[Ix]; if (!TestIsNormalizedImpl(test.path, test.allow_all, test.normalized, test.len, test.result)) { return false; } if constexpr (Ix < N) { return DoIsNormalizedTests(tests); } else { AMS_ASSUME(false); } } consteval bool TestIsNormalized() { constexpr IsNormalizedTestData Tests[] = { { "/aa/bb/c/", false, false, 9, ResultSuccess() }, { "aa/bb/c/", false, false, 0, fs::ResultInvalidPathFormat() }, { "aa/bb/c/", false, false, 0, fs::ResultInvalidPathFormat() }, { "mount:a/b", false, false, 0, fs::ResultInvalidPathFormat() }, { "mo|unt:a/b", true, false, 0, fs::ResultInvalidPathFormat() }, { "/aa/bb/../..", false, false, 0, ResultSuccess() }, { "/aa/bb/../../..", false, false, 0, ResultSuccess() }, { "/aa/bb/../../..", false, false, 0, ResultSuccess() }, { "aa/bb/../../..", false, false, 0, fs::ResultInvalidPathFormat() }, { "aa/bb/../../..", false, false, 0, fs::ResultInvalidPathFormat() }, { "mount:a/b", true, false, 0, fs::ResultInvalidPathFormat() }, { "/a|/bb/cc", true, true, 9, ResultSuccess() }, { "/>a/bb/cc", true, true, 9, ResultSuccess() }, { "/aa/.a/bb/cc", false, false, 0, fs::ResultInvalidCharacter() }, { "/aa/.