fs_path_utility.h 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240
  1. // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #pragma once
  4. #include "common/assert.h"
  5. #include "common/common_funcs.h"
  6. #include "common/common_types.h"
  7. #include "common/scope_exit.h"
  8. #include "core/file_sys/fs_directory.h"
  9. #include "core/file_sys/fs_memory_management.h"
  10. #include "core/file_sys/fs_string_util.h"
  11. #include "core/hle/result.h"
  12. namespace FileSys {
  13. constexpr inline size_t MountNameLengthMax = 15;
  14. namespace StringTraits {
  15. constexpr inline char DirectorySeparator = '/';
  16. constexpr inline char DriveSeparator = ':';
  17. constexpr inline char Dot = '.';
  18. constexpr inline char NullTerminator = '\x00';
  19. constexpr inline char AlternateDirectorySeparator = '\\';
  20. constexpr inline const char InvalidCharacters[6] = {':', '*', '?', '<', '>', '|'};
  21. constexpr inline const char InvalidCharactersForHostName[6] = {':', '*', '<', '>', '|', '$'};
  22. constexpr inline const char InvalidCharactersForMountName[5] = {'*', '?', '<', '>', '|'};
  23. namespace impl {
  24. template <const char* InvalidCharacterSet, size_t NumInvalidCharacters>
  25. consteval u64 MakeInvalidCharacterMask(size_t n) {
  26. u64 mask = 0;
  27. for (size_t i = 0; i < NumInvalidCharacters; ++i) {
  28. if ((static_cast<u64>(InvalidCharacterSet[i]) >> 6) == n) {
  29. mask |= static_cast<u64>(1) << (static_cast<u64>(InvalidCharacterSet[i]) & 0x3F);
  30. }
  31. }
  32. return mask;
  33. }
  34. template <const char* InvalidCharacterSet, size_t NumInvalidCharacters>
  35. constexpr bool IsInvalidCharacterImpl(char c) {
  36. constexpr u64 Masks[4] = {
  37. MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(0),
  38. MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(1),
  39. MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(2),
  40. MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(3)};
  41. return (Masks[static_cast<u64>(c) >> 6] &
  42. (static_cast<u64>(1) << (static_cast<u64>(c) & 0x3F))) != 0;
  43. }
  44. } // namespace impl
  45. constexpr bool IsInvalidCharacter(char c) {
  46. return impl::IsInvalidCharacterImpl<InvalidCharacters, Common::Size(InvalidCharacters)>(c);
  47. }
  48. constexpr bool IsInvalidCharacterForHostName(char c) {
  49. return impl::IsInvalidCharacterImpl<InvalidCharactersForHostName,
  50. Common::Size(InvalidCharactersForHostName)>(c);
  51. }
  52. constexpr bool IsInvalidCharacterForMountName(char c) {
  53. return impl::IsInvalidCharacterImpl<InvalidCharactersForMountName,
  54. Common::Size(InvalidCharactersForMountName)>(c);
  55. }
  56. } // namespace StringTraits
  57. constexpr inline size_t WindowsDriveLength = 2;
  58. constexpr inline size_t UncPathPrefixLength = 2;
  59. constexpr inline size_t DosDevicePathPrefixLength = 4;
  60. class PathFlags {
  61. private:
  62. static constexpr u32 WindowsPathFlag = (1 << 0);
  63. static constexpr u32 RelativePathFlag = (1 << 1);
  64. static constexpr u32 EmptyPathFlag = (1 << 2);
  65. static constexpr u32 MountNameFlag = (1 << 3);
  66. static constexpr u32 BackslashFlag = (1 << 4);
  67. static constexpr u32 AllCharactersFlag = (1 << 5);
  68. private:
  69. u32 m_value;
  70. public:
  71. constexpr PathFlags() : m_value(0) { /* ... */
  72. }
  73. #define DECLARE_PATH_FLAG_HANDLER(__WHICH__) \
  74. constexpr bool Is##__WHICH__##Allowed() const { return (m_value & __WHICH__##Flag) != 0; } \
  75. constexpr void Allow##__WHICH__() { m_value |= __WHICH__##Flag; }
  76. DECLARE_PATH_FLAG_HANDLER(WindowsPath)
  77. DECLARE_PATH_FLAG_HANDLER(RelativePath)
  78. DECLARE_PATH_FLAG_HANDLER(EmptyPath)
  79. DECLARE_PATH_FLAG_HANDLER(MountName)
  80. DECLARE_PATH_FLAG_HANDLER(Backslash)
  81. DECLARE_PATH_FLAG_HANDLER(AllCharacters)
  82. #undef DECLARE_PATH_FLAG_HANDLER
  83. };
  84. template <typename T>
  85. requires(std::same_as<T, char> || std::same_as<T, wchar_t>)
  86. constexpr inline bool IsDosDevicePath(const T* path) {
  87. ASSERT(path != nullptr);
  88. using namespace StringTraits;
  89. return path[0] == AlternateDirectorySeparator && path[1] == AlternateDirectorySeparator &&
  90. (path[2] == Dot || path[2] == '?') &&
  91. (path[3] == DirectorySeparator || path[3] == AlternateDirectorySeparator);
  92. }
  93. template <typename T>
  94. requires(std::same_as<T, char> || std::same_as<T, wchar_t>)
  95. constexpr inline bool IsUncPath(const T* path, bool allow_forward_slash = true,
  96. bool allow_back_slash = true) {
  97. ASSERT(path != nullptr);
  98. using namespace StringTraits;
  99. return (allow_forward_slash && path[0] == DirectorySeparator &&
  100. path[1] == DirectorySeparator) ||
  101. (allow_back_slash && path[0] == AlternateDirectorySeparator &&
  102. path[1] == AlternateDirectorySeparator);
  103. }
  104. constexpr inline bool IsWindowsDrive(const char* path) {
  105. ASSERT(path != nullptr);
  106. return (('a' <= path[0] && path[0] <= 'z') || ('A' <= path[0] && path[0] <= 'Z')) &&
  107. path[1] == StringTraits::DriveSeparator;
  108. }
  109. constexpr inline bool IsWindowsPath(const char* path, bool allow_forward_slash_unc) {
  110. return IsWindowsDrive(path) || IsDosDevicePath(path) ||
  111. IsUncPath(path, allow_forward_slash_unc, true);
  112. }
  113. constexpr inline int GetWindowsSkipLength(const char* path) {
  114. if (IsDosDevicePath(path)) {
  115. return DosDevicePathPrefixLength;
  116. } else if (IsWindowsDrive(path)) {
  117. return WindowsDriveLength;
  118. } else if (IsUncPath(path)) {
  119. return UncPathPrefixLength;
  120. } else {
  121. return 0;
  122. }
  123. }
  124. constexpr inline bool IsPathAbsolute(const char* path) {
  125. return IsWindowsPath(path, false) || path[0] == StringTraits::DirectorySeparator;
  126. }
  127. constexpr inline bool IsPathRelative(const char* path) {
  128. return path[0] && !IsPathAbsolute(path);
  129. }
  130. constexpr inline bool IsCurrentDirectory(const char* path) {
  131. return path[0] == StringTraits::Dot &&
  132. (path[1] == StringTraits::NullTerminator || path[1] == StringTraits::DirectorySeparator);
  133. }
  134. constexpr inline bool IsParentDirectory(const char* path) {
  135. return path[0] == StringTraits::Dot && path[1] == StringTraits::Dot &&
  136. (path[2] == StringTraits::NullTerminator || path[2] == StringTraits::DirectorySeparator);
  137. }
  138. constexpr inline bool IsPathStartWithCurrentDirectory(const char* path) {
  139. return IsCurrentDirectory(path) || IsParentDirectory(path);
  140. }
  141. constexpr inline bool IsSubPath(const char* lhs, const char* rhs) {
  142. // Check pre-conditions
  143. ASSERT(lhs != nullptr);
  144. ASSERT(rhs != nullptr);
  145. // Import StringTraits names for current scope
  146. using namespace StringTraits;
  147. // Special case certain paths
  148. if (IsUncPath(lhs) && !IsUncPath(rhs)) {
  149. return false;
  150. }
  151. if (!IsUncPath(lhs) && IsUncPath(rhs)) {
  152. return false;
  153. }
  154. if (lhs[0] == DirectorySeparator && lhs[1] == NullTerminator && rhs[0] == DirectorySeparator &&
  155. rhs[1] != NullTerminator) {
  156. return true;
  157. }
  158. if (rhs[0] == DirectorySeparator && rhs[1] == NullTerminator && lhs[0] == DirectorySeparator &&
  159. lhs[1] != NullTerminator) {
  160. return true;
  161. }
  162. // Check subpath
  163. for (size_t i = 0; /* ... */; ++i) {
  164. if (lhs[i] == NullTerminator) {
  165. return rhs[i] == DirectorySeparator;
  166. } else if (rhs[i] == NullTerminator) {
  167. return lhs[i] == DirectorySeparator;
  168. } else if (lhs[i] != rhs[i]) {
  169. return false;
  170. }
  171. }
  172. }
  173. // Path utilities
  174. constexpr inline void Replace(char* dst, size_t dst_size, char old_char, char new_char) {
  175. ASSERT(dst != nullptr);
  176. for (char* cur = dst; cur < dst + dst_size && *cur; ++cur) {
  177. if (*cur == old_char) {
  178. *cur = new_char;
  179. }
  180. }
  181. }
  182. constexpr inline Result CheckUtf8(const char* s) {
  183. // Check pre-conditions
  184. ASSERT(s != nullptr);
  185. // Iterate, checking for utf8-validity
  186. while (*s) {
  187. char utf8_buf[4] = {};
  188. const auto pick_res = PickOutCharacterFromUtf8String(utf8_buf, std::addressof(s));
  189. R_UNLESS(pick_res == CharacterEncodingResult_Success, ResultInvalidPathFormat);
  190. u32 dummy;
  191. const auto cvt_res = ConvertCharacterUtf8ToUtf32(std::addressof(dummy), utf8_buf);
  192. R_UNLESS(cvt_res == CharacterEncodingResult_Success, ResultInvalidPathFormat);
  193. }
  194. R_SUCCEED();
  195. }
  196. // Path formatting
  197. class PathNormalizer {
  198. private:
  199. enum class PathState {
  200. Start,
  201. Normal,
  202. FirstSeparator,
  203. Separator,
  204. CurrentDir,
  205. ParentDir,
  206. };
  207. private:
  208. static constexpr void ReplaceParentDirectoryPath(char* dst, const char* src) {
  209. // Use StringTraits names for remainder of scope
  210. using namespace StringTraits;
  211. // Start with a dir-separator
  212. dst[0] = DirectorySeparator;
  213. auto i = 1;
  214. while (src[i] != NullTerminator) {
  215. if ((src[i - 1] == DirectorySeparator || src[i - 1] == AlternateDirectorySeparator) &&
  216. src[i + 0] == Dot && src[i + 1] == Dot &&
  217. (src[i + 2] == DirectorySeparator || src[i + 2] == AlternateDirectorySeparator)) {
  218. dst[i - 1] = DirectorySeparator;
  219. dst[i + 0] = Dot;
  220. dst[i + 1] = Dot;
  221. dst[i + 2] = DirectorySeparator;
  222. i += 3;
  223. } else {
  224. if (src[i - 1] == AlternateDirectorySeparator && src[i + 0] == Dot &&
  225. src[i + 1] == Dot && src[i + 2] == NullTerminator) {
  226. dst[i - 1] = DirectorySeparator;
  227. dst[i + 0] = Dot;
  228. dst[i + 1] = Dot;
  229. i += 2;
  230. break;
  231. }
  232. dst[i] = src[i];
  233. ++i;
  234. }
  235. }
  236. dst[i] = StringTraits::NullTerminator;
  237. }
  238. public:
  239. static constexpr bool IsParentDirectoryPathReplacementNeeded(const char* path) {
  240. // Use StringTraits names for remainder of scope
  241. using namespace StringTraits;
  242. if (path[0] != DirectorySeparator && path[0] != AlternateDirectorySeparator) {
  243. return false;
  244. }
  245. // Check to find a parent reference using alternate separators
  246. if (path[0] != NullTerminator && path[1] != NullTerminator && path[2] != NullTerminator) {
  247. size_t i;
  248. for (i = 0; path[i + 3] != NullTerminator; ++path) {
  249. if (path[i + 1] != Dot || path[i + 2] != Dot) {
  250. continue;
  251. }
  252. const char c0 = path[i + 0];
  253. const char c3 = path[i + 3];
  254. if (c0 == AlternateDirectorySeparator &&
  255. (c3 == DirectorySeparator || c3 == AlternateDirectorySeparator ||
  256. c3 == NullTerminator)) {
  257. return true;
  258. }
  259. if (c3 == AlternateDirectorySeparator &&
  260. (c0 == DirectorySeparator || c0 == AlternateDirectorySeparator)) {
  261. return true;
  262. }
  263. }
  264. if (path[i + 0] == AlternateDirectorySeparator && path[i + 1] == Dot &&
  265. path[i + 2] == Dot /* && path[i + 3] == NullTerminator */) {
  266. return true;
  267. }
  268. }
  269. return false;
  270. }
  271. static constexpr Result IsNormalized(bool* out, size_t* out_len, const char* path,
  272. bool allow_all_characters = false) {
  273. // Use StringTraits names for remainder of scope
  274. using namespace StringTraits;
  275. // Parse the path
  276. auto state = PathState::Start;
  277. size_t len = 0;
  278. while (path[len] != NullTerminator) {
  279. // Get the current character
  280. const char c = path[len++];
  281. // Check the current character is valid
  282. if (!allow_all_characters && state != PathState::Start) {
  283. R_UNLESS(!IsInvalidCharacter(c), ResultInvalidCharacter);
  284. }
  285. // Process depending on current state
  286. switch (state) {
  287. // Import the PathState enums for convenience
  288. using enum PathState;
  289. case Start:
  290. R_UNLESS(c == DirectorySeparator, ResultInvalidPathFormat);
  291. state = FirstSeparator;
  292. break;
  293. case Normal:
  294. if (c == DirectorySeparator) {
  295. state = Separator;
  296. }
  297. break;
  298. case FirstSeparator:
  299. case Separator:
  300. if (c == DirectorySeparator) {
  301. *out = false;
  302. R_SUCCEED();
  303. }
  304. if (c == Dot) {
  305. state = CurrentDir;
  306. } else {
  307. state = Normal;
  308. }
  309. break;
  310. case CurrentDir:
  311. if (c == DirectorySeparator) {
  312. *out = false;
  313. R_SUCCEED();
  314. }
  315. if (c == Dot) {
  316. state = ParentDir;
  317. } else {
  318. state = Normal;
  319. }
  320. break;
  321. case ParentDir:
  322. if (c == DirectorySeparator) {
  323. *out = false;
  324. R_SUCCEED();
  325. }
  326. state = Normal;
  327. break;
  328. default:
  329. UNREACHABLE();
  330. break;
  331. }
  332. }
  333. // Check the final state
  334. switch (state) {
  335. // Import the PathState enums for convenience
  336. using enum PathState;
  337. case Start:
  338. R_THROW(ResultInvalidPathFormat);
  339. case Normal:
  340. case FirstSeparator:
  341. *out = true;
  342. break;
  343. case Separator:
  344. case CurrentDir:
  345. case ParentDir:
  346. *out = false;
  347. break;
  348. default:
  349. UNREACHABLE();
  350. break;
  351. }
  352. // Set the output length
  353. *out_len = len;
  354. R_SUCCEED();
  355. }
  356. static constexpr Result Normalize(char* dst, size_t* out_len, const char* path,
  357. size_t max_out_size, bool is_windows_path,
  358. bool is_drive_relative_path,
  359. bool allow_all_characters = false) {
  360. // Use StringTraits names for remainder of scope
  361. using namespace StringTraits;
  362. // Prepare to iterate
  363. const char* cur_path = path;
  364. size_t total_len = 0;
  365. // If path begins with a separator, check that we're not drive relative
  366. if (cur_path[0] != DirectorySeparator) {
  367. R_UNLESS(is_drive_relative_path, ResultInvalidPathFormat);
  368. dst[total_len++] = DirectorySeparator;
  369. }
  370. // We're going to need to do path replacement, potentially
  371. char* replacement_path = nullptr;
  372. size_t replacement_path_size = 0;
  373. SCOPE_EXIT {
  374. if (replacement_path != nullptr) {
  375. if (std::is_constant_evaluated()) {
  376. delete[] replacement_path;
  377. } else {
  378. Deallocate(replacement_path, replacement_path_size);
  379. }
  380. }
  381. };
  382. // Perform path replacement, if necessary
  383. if (IsParentDirectoryPathReplacementNeeded(cur_path)) {
  384. if (std::is_constant_evaluated()) {
  385. replacement_path_size = EntryNameLengthMax + 1;
  386. replacement_path = new char[replacement_path_size];
  387. } else {
  388. replacement_path_size = EntryNameLengthMax + 1;
  389. replacement_path = static_cast<char*>(Allocate(replacement_path_size));
  390. }
  391. ReplaceParentDirectoryPath(replacement_path, cur_path);
  392. cur_path = replacement_path;
  393. }
  394. // Iterate, normalizing path components
  395. bool skip_next_sep = false;
  396. size_t i = 0;
  397. while (cur_path[i] != NullTerminator) {
  398. // Process a directory separator, if we run into one
  399. if (cur_path[i] == DirectorySeparator) {
  400. // Swallow separators
  401. do {
  402. ++i;
  403. } while (cur_path[i] == DirectorySeparator);
  404. // Check if we hit end of string
  405. if (cur_path[i] == NullTerminator) {
  406. break;
  407. }
  408. // If we aren't skipping the separator, write it, checking that we remain in bounds.
  409. if (!skip_next_sep) {
  410. if (total_len + 1 == max_out_size) {
  411. dst[total_len] = NullTerminator;
  412. *out_len = total_len;
  413. R_THROW(ResultTooLongPath);
  414. }
  415. dst[total_len++] = DirectorySeparator;
  416. }
  417. // Don't skip the next separator
  418. skip_next_sep = false;
  419. }
  420. // Get the length of the current directory component
  421. size_t dir_len = 0;
  422. while (cur_path[i + dir_len] != DirectorySeparator &&
  423. cur_path[i + dir_len] != NullTerminator) {
  424. // Check for validity
  425. if (!allow_all_characters) {
  426. R_UNLESS(!IsInvalidCharacter(cur_path[i + dir_len]), ResultInvalidCharacter);
  427. }
  428. ++dir_len;
  429. }
  430. // Handle the current dir component
  431. if (IsCurrentDirectory(cur_path + i)) {
  432. skip_next_sep = true;
  433. } else if (IsParentDirectory(cur_path + i)) {
  434. // We should have just written a separator
  435. ASSERT(dst[total_len - 1] == DirectorySeparator);
  436. // We should have started with a separator, for non-windows paths
  437. if (!is_windows_path) {
  438. ASSERT(dst[0] == DirectorySeparator);
  439. }
  440. // Remove the previous component
  441. if (total_len == 1) {
  442. R_UNLESS(is_windows_path, ResultDirectoryUnobtainable);
  443. --total_len;
  444. } else {
  445. total_len -= 2;
  446. do {
  447. if (dst[total_len] == DirectorySeparator) {
  448. break;
  449. }
  450. } while ((--total_len) != 0);
  451. }
  452. // We should be pointing to a directory separator, for non-windows paths
  453. if (!is_windows_path) {
  454. ASSERT(dst[total_len] == DirectorySeparator);
  455. }
  456. // We should remain in bounds
  457. ASSERT(total_len < max_out_size);
  458. } else {
  459. // Copy, possibly truncating
  460. if (total_len + dir_len + 1 > max_out_size) {
  461. const size_t copy_len = max_out_size - (total_len + 1);
  462. for (size_t j = 0; j < copy_len; ++j) {
  463. dst[total_len++] = cur_path[i + j];
  464. }
  465. dst[total_len] = NullTerminator;
  466. *out_len = total_len;
  467. R_THROW(ResultTooLongPath);
  468. }
  469. for (size_t j = 0; j < dir_len; ++j) {
  470. dst[total_len++] = cur_path[i + j];
  471. }
  472. }
  473. // Advance past the current directory component
  474. i += dir_len;
  475. }
  476. if (skip_next_sep) {
  477. --total_len;
  478. }
  479. if (total_len == 0 && max_out_size != 0) {
  480. total_len = 1;
  481. dst[0] = DirectorySeparator;
  482. }
  483. // NOTE: Probable nintendo bug, as max_out_size must be at least total_len + 1 for the null
  484. // terminator.
  485. R_UNLESS(max_out_size >= total_len - 1, ResultTooLongPath);
  486. dst[total_len] = NullTerminator;
  487. // Check that the result path is normalized
  488. bool is_normalized;
  489. size_t dummy;
  490. R_TRY(IsNormalized(std::addressof(is_normalized), std::addressof(dummy), dst,
  491. allow_all_characters));
  492. // Assert that the result path is normalized
  493. ASSERT(is_normalized);
  494. // Set the output length
  495. *out_len = total_len;
  496. R_SUCCEED();
  497. }
  498. };
  499. class PathFormatter {
  500. private:
  501. static constexpr Result CheckSharedName(const char* name, size_t len) {
  502. // Use StringTraits names for remainder of scope
  503. using namespace StringTraits;
  504. if (len == 1) {
  505. R_UNLESS(name[0] != Dot, ResultInvalidPathFormat);
  506. } else if (len == 2) {
  507. R_UNLESS(name[0] != Dot || name[1] != Dot, ResultInvalidPathFormat);
  508. }
  509. for (size_t i = 0; i < len; ++i) {
  510. R_UNLESS(!IsInvalidCharacter(name[i]), ResultInvalidCharacter);
  511. }
  512. R_SUCCEED();
  513. }
  514. static constexpr Result CheckHostName(const char* name, size_t len) {
  515. // Use StringTraits names for remainder of scope
  516. using namespace StringTraits;
  517. if (len == 2) {
  518. R_UNLESS(name[0] != Dot || name[1] != Dot, ResultInvalidPathFormat);
  519. }
  520. for (size_t i = 0; i < len; ++i) {
  521. R_UNLESS(!IsInvalidCharacterForHostName(name[i]), ResultInvalidCharacter);
  522. }
  523. R_SUCCEED();
  524. }
  525. static constexpr Result CheckInvalidBackslash(bool* out_contains_backslash, const char* path,
  526. bool allow_backslash) {
  527. // Use StringTraits names for remainder of scope
  528. using namespace StringTraits;
  529. // Default to no backslashes, so we can just write if we see one
  530. *out_contains_backslash = false;
  531. while (*path != NullTerminator) {
  532. if (*(path++) == AlternateDirectorySeparator) {
  533. *out_contains_backslash = true;
  534. R_UNLESS(allow_backslash, ResultInvalidCharacter);
  535. }
  536. }
  537. R_SUCCEED();
  538. }
  539. public:
  540. static constexpr Result CheckPathFormat(const char* path, const PathFlags& flags) {
  541. bool normalized;
  542. size_t len;
  543. R_RETURN(IsNormalized(std::addressof(normalized), std::addressof(len), path, flags));
  544. }
  545. static constexpr Result SkipMountName(const char** out, size_t* out_len, const char* path) {
  546. R_RETURN(ParseMountName(out, out_len, nullptr, 0, path));
  547. }
  548. static constexpr Result ParseMountName(const char** out, size_t* out_len, char* out_mount_name,
  549. size_t out_mount_name_buffer_size, const char* path) {
  550. // Check pre-conditions
  551. ASSERT(path != nullptr);
  552. ASSERT(out_len != nullptr);
  553. ASSERT(out != nullptr);
  554. ASSERT((out_mount_name == nullptr) == (out_mount_name_buffer_size == 0));
  555. // Use StringTraits names for remainder of scope
  556. using namespace StringTraits;
  557. // Determine max mount length
  558. const auto max_mount_len =
  559. out_mount_name_buffer_size == 0
  560. ? MountNameLengthMax + 1
  561. : std::min(MountNameLengthMax + 1, out_mount_name_buffer_size);
  562. // Parse the path until we see a drive separator
  563. size_t mount_len = 0;
  564. for (/* ... */; mount_len < max_mount_len && path[mount_len]; ++mount_len) {
  565. const char c = path[mount_len];
  566. // If we see a drive separator, advance, then we're done with the pre-drive separator
  567. // part of the mount.
  568. if (c == DriveSeparator) {
  569. ++mount_len;
  570. break;
  571. }
  572. // If we see a directory separator, we're not in a mount name
  573. if (c == DirectorySeparator || c == AlternateDirectorySeparator) {
  574. *out = path;
  575. *out_len = 0;
  576. R_SUCCEED();
  577. }
  578. }
  579. // Check to be sure we're actually looking at a mount name
  580. if (mount_len <= 2 || path[mount_len - 1] != DriveSeparator) {
  581. *out = path;
  582. *out_len = 0;
  583. R_SUCCEED();
  584. }
  585. // Check that all characters in the mount name are allowable
  586. for (size_t i = 0; i < mount_len; ++i) {
  587. R_UNLESS(!IsInvalidCharacterForMountName(path[i]), ResultInvalidCharacter);
  588. }
  589. // Copy out the mount name
  590. if (out_mount_name_buffer_size > 0) {
  591. R_UNLESS(mount_len < out_mount_name_buffer_size, ResultTooLongPath);
  592. for (size_t i = 0; i < mount_len; ++i) {
  593. out_mount_name[i] = path[i];
  594. }
  595. out_mount_name[mount_len] = NullTerminator;
  596. }
  597. // Set the output
  598. *out = path + mount_len;
  599. *out_len = mount_len;
  600. R_SUCCEED();
  601. }
  602. static constexpr Result SkipRelativeDotPath(const char** out, size_t* out_len,
  603. const char* path) {
  604. R_RETURN(ParseRelativeDotPath(out, out_len, nullptr, 0, path));
  605. }
  606. static constexpr Result ParseRelativeDotPath(const char** out, size_t* out_len,
  607. char* out_relative,
  608. size_t out_relative_buffer_size,
  609. const char* path) {
  610. // Check pre-conditions
  611. ASSERT(path != nullptr);
  612. ASSERT(out_len != nullptr);
  613. ASSERT(out != nullptr);
  614. ASSERT((out_relative == nullptr) == (out_relative_buffer_size == 0));
  615. // Use StringTraits names for remainder of scope
  616. using namespace StringTraits;
  617. // Initialize the output buffer, if we have one
  618. if (out_relative_buffer_size > 0) {
  619. out_relative[0] = NullTerminator;
  620. }
  621. // Check if the path is relative
  622. if (path[0] == Dot && (path[1] == NullTerminator || path[1] == DirectorySeparator ||
  623. path[1] == AlternateDirectorySeparator)) {
  624. if (out_relative_buffer_size > 0) {
  625. R_UNLESS(out_relative_buffer_size >= 2, ResultTooLongPath);
  626. out_relative[0] = Dot;
  627. out_relative[1] = NullTerminator;
  628. }
  629. *out = path + 1;
  630. *out_len = 1;
  631. R_SUCCEED();
  632. }
  633. // Ensure the path isn't a parent directory
  634. R_UNLESS(!(path[0] == Dot && path[1] == Dot), ResultDirectoryUnobtainable);
  635. // There was no relative dot path
  636. *out = path;
  637. *out_len = 0;
  638. R_SUCCEED();
  639. }
  640. static constexpr Result SkipWindowsPath(const char** out, size_t* out_len, bool* out_normalized,
  641. const char* path, bool has_mount_name) {
  642. // We're normalized if and only if the parsing doesn't throw ResultNotNormalized()
  643. *out_normalized = true;
  644. R_TRY_CATCH(ParseWindowsPath(out, out_len, nullptr, 0, path, has_mount_name)) {
  645. R_CATCH(ResultNotNormalized) {
  646. *out_normalized = false;
  647. }
  648. }
  649. R_END_TRY_CATCH;
  650. ON_RESULT_INCLUDED(ResultNotNormalized) {
  651. *out_normalized = false;
  652. };
  653. R_SUCCEED();
  654. }
  655. static constexpr Result ParseWindowsPath(const char** out, size_t* out_len, char* out_win,
  656. size_t out_win_buffer_size, const char* path,
  657. bool has_mount_name) {
  658. // Check pre-conditions
  659. ASSERT(path != nullptr);
  660. ASSERT(out_len != nullptr);
  661. ASSERT(out != nullptr);
  662. ASSERT((out_win == nullptr) == (out_win_buffer_size == 0));
  663. // Use StringTraits names for remainder of scope
  664. using namespace StringTraits;
  665. // Initialize the output buffer, if we have one
  666. if (out_win_buffer_size > 0) {
  667. out_win[0] = NullTerminator;
  668. }
  669. // Handle path start
  670. const char* cur_path = path;
  671. if (has_mount_name && path[0] == DirectorySeparator) {
  672. if (path[1] == AlternateDirectorySeparator && path[2] == AlternateDirectorySeparator) {
  673. R_UNLESS(out_win_buffer_size > 0, ResultNotNormalized);
  674. ++cur_path;
  675. } else if (IsWindowsDrive(path + 1)) {
  676. R_UNLESS(out_win_buffer_size > 0, ResultNotNormalized);
  677. ++cur_path;
  678. }
  679. }
  680. // Handle windows drive
  681. if (IsWindowsDrive(cur_path)) {
  682. // Parse up to separator
  683. size_t win_path_len = WindowsDriveLength;
  684. for (/* ... */; cur_path[win_path_len] != NullTerminator; ++win_path_len) {
  685. R_UNLESS(!IsInvalidCharacter(cur_path[win_path_len]), ResultInvalidCharacter);
  686. if (cur_path[win_path_len] == DirectorySeparator ||
  687. cur_path[win_path_len] == AlternateDirectorySeparator) {
  688. break;
  689. }
  690. }
  691. // Ensure that we're normalized, if we're required to be
  692. if (out_win_buffer_size == 0) {
  693. for (size_t i = 0; i < win_path_len; ++i) {
  694. R_UNLESS(cur_path[i] != AlternateDirectorySeparator, ResultNotNormalized);
  695. }
  696. } else {
  697. // Ensure we can copy into the normalized buffer
  698. R_UNLESS(win_path_len < out_win_buffer_size, ResultTooLongPath);
  699. for (size_t i = 0; i < win_path_len; ++i) {
  700. out_win[i] = cur_path[i];
  701. }
  702. out_win[win_path_len] = NullTerminator;
  703. Replace(out_win, win_path_len, AlternateDirectorySeparator, DirectorySeparator);
  704. }
  705. *out = cur_path + win_path_len;
  706. *out_len = win_path_len;
  707. R_SUCCEED();
  708. }
  709. // Handle DOS device
  710. if (IsDosDevicePath(cur_path)) {
  711. size_t dos_prefix_len = DosDevicePathPrefixLength;
  712. if (IsWindowsDrive(cur_path + dos_prefix_len)) {
  713. dos_prefix_len += WindowsDriveLength;
  714. } else {
  715. --dos_prefix_len;
  716. }
  717. if (out_win_buffer_size > 0) {
  718. // Ensure we can copy into the normalized buffer
  719. R_UNLESS(dos_prefix_len < out_win_buffer_size, ResultTooLongPath);
  720. for (size_t i = 0; i < dos_prefix_len; ++i) {
  721. out_win[i] = cur_path[i];
  722. }
  723. out_win[dos_prefix_len] = NullTerminator;
  724. Replace(out_win, dos_prefix_len, DirectorySeparator, AlternateDirectorySeparator);
  725. }
  726. *out = cur_path + dos_prefix_len;
  727. *out_len = dos_prefix_len;
  728. R_SUCCEED();
  729. }
  730. // Handle UNC path
  731. if (IsUncPath(cur_path, false, true)) {
  732. const char* final_path = cur_path;
  733. R_UNLESS(cur_path[UncPathPrefixLength] != DirectorySeparator, ResultInvalidPathFormat);
  734. R_UNLESS(cur_path[UncPathPrefixLength] != AlternateDirectorySeparator,
  735. ResultInvalidPathFormat);
  736. size_t cur_component_offset = 0;
  737. size_t pos = UncPathPrefixLength;
  738. for (/* ... */; cur_path[pos] != NullTerminator; ++pos) {
  739. if (cur_path[pos] == DirectorySeparator ||
  740. cur_path[pos] == AlternateDirectorySeparator) {
  741. if (cur_component_offset != 0) {
  742. R_TRY(CheckSharedName(cur_path + cur_component_offset,
  743. pos - cur_component_offset));
  744. final_path = cur_path + pos;
  745. break;
  746. }
  747. R_UNLESS(cur_path[pos + 1] != DirectorySeparator, ResultInvalidPathFormat);
  748. R_UNLESS(cur_path[pos + 1] != AlternateDirectorySeparator,
  749. ResultInvalidPathFormat);
  750. R_TRY(CheckHostName(cur_path + 2, pos - 2));
  751. cur_component_offset = pos + 1;
  752. }
  753. }
  754. R_UNLESS(cur_component_offset != pos, ResultInvalidPathFormat);
  755. if (cur_component_offset != 0 && final_path == cur_path) {
  756. R_TRY(CheckSharedName(cur_path + cur_component_offset, pos - cur_component_offset));
  757. final_path = cur_path + pos;
  758. }
  759. size_t unc_prefix_len = final_path - cur_path;
  760. // Ensure that we're normalized, if we're required to be
  761. if (out_win_buffer_size == 0) {
  762. for (size_t i = 0; i < unc_prefix_len; ++i) {
  763. R_UNLESS(cur_path[i] != DirectorySeparator, ResultNotNormalized);
  764. }
  765. } else {
  766. // Ensure we can copy into the normalized buffer
  767. R_UNLESS(unc_prefix_len < out_win_buffer_size, ResultTooLongPath);
  768. for (size_t i = 0; i < unc_prefix_len; ++i) {
  769. out_win[i] = cur_path[i];
  770. }
  771. out_win[unc_prefix_len] = NullTerminator;
  772. Replace(out_win, unc_prefix_len, DirectorySeparator, AlternateDirectorySeparator);
  773. }
  774. *out = cur_path + unc_prefix_len;
  775. *out_len = unc_prefix_len;
  776. R_SUCCEED();
  777. }
  778. // There's no windows path to parse
  779. *out = path;
  780. *out_len = 0;
  781. R_SUCCEED();
  782. }
  783. static constexpr Result IsNormalized(bool* out, size_t* out_len, const char* path,
  784. const PathFlags& flags = {}) {
  785. // Ensure nothing is null
  786. R_UNLESS(out != nullptr, ResultNullptrArgument);
  787. R_UNLESS(out_len != nullptr, ResultNullptrArgument);
  788. R_UNLESS(path != nullptr, ResultNullptrArgument);
  789. // Verify that the path is valid utf-8
  790. R_TRY(CheckUtf8(path));
  791. // Use StringTraits names for remainder of scope
  792. using namespace StringTraits;
  793. // Handle the case where the path is empty
  794. if (path[0] == NullTerminator) {
  795. R_UNLESS(flags.IsEmptyPathAllowed(), ResultInvalidPathFormat);
  796. *out = true;
  797. *out_len = 0;
  798. R_SUCCEED();
  799. }
  800. // All normalized paths start with a directory separator...unless they're windows paths,
  801. // relative paths, or have mount names.
  802. if (path[0] != DirectorySeparator) {
  803. R_UNLESS(flags.IsWindowsPathAllowed() || flags.IsRelativePathAllowed() ||
  804. flags.IsMountNameAllowed(),
  805. ResultInvalidPathFormat);
  806. }
  807. // Check that the path is allowed to be a windows path, if it is
  808. if (IsWindowsPath(path, false)) {
  809. R_UNLESS(flags.IsWindowsPathAllowed(), ResultInvalidPathFormat);
  810. }
  811. // Skip past the mount name, if one is present
  812. size_t total_len = 0;
  813. size_t mount_name_len = 0;
  814. R_TRY(SkipMountName(std::addressof(path), std::addressof(mount_name_len), path));
  815. // If we had a mount name, check that that was allowed
  816. if (mount_name_len > 0) {
  817. R_UNLESS(flags.IsMountNameAllowed(), ResultInvalidPathFormat);
  818. total_len += mount_name_len;
  819. }
  820. // Check that the path starts as a normalized path should
  821. if (path[0] != DirectorySeparator && !IsPathStartWithCurrentDirectory(path) &&
  822. !IsWindowsPath(path, false)) {
  823. R_UNLESS(flags.IsRelativePathAllowed(), ResultInvalidPathFormat);
  824. R_UNLESS(!IsInvalidCharacter(path[0]), ResultInvalidPathFormat);
  825. *out = false;
  826. R_SUCCEED();
  827. }
  828. // Process relative path
  829. size_t relative_len = 0;
  830. R_TRY(SkipRelativeDotPath(std::addressof(path), std::addressof(relative_len), path));
  831. // If we have a relative path, check that was allowed
  832. if (relative_len > 0) {
  833. R_UNLESS(flags.IsRelativePathAllowed(), ResultInvalidPathFormat);
  834. total_len += relative_len;
  835. if (path[0] == NullTerminator) {
  836. *out = true;
  837. *out_len = total_len;
  838. R_SUCCEED();
  839. }
  840. }
  841. // Process windows path
  842. size_t windows_len = 0;
  843. bool normalized_win = false;
  844. R_TRY(SkipWindowsPath(std::addressof(path), std::addressof(windows_len),
  845. std::addressof(normalized_win), path, mount_name_len > 0));
  846. // If the windows path wasn't normalized, we're not normalized
  847. if (!normalized_win) {
  848. R_UNLESS(flags.IsWindowsPathAllowed(), ResultInvalidPathFormat);
  849. *out = false;
  850. R_SUCCEED();
  851. }
  852. // If we had a windows path, check that was allowed
  853. if (windows_len > 0) {
  854. R_UNLESS(flags.IsWindowsPathAllowed(), ResultInvalidPathFormat);
  855. total_len += windows_len;
  856. // We can't have both a relative path and a windows path
  857. R_UNLESS(relative_len == 0, ResultInvalidPathFormat);
  858. // A path ending in a windows path isn't normalized
  859. if (path[0] == NullTerminator) {
  860. *out = false;
  861. R_SUCCEED();
  862. }
  863. // Check that there are no windows directory separators in the path
  864. for (size_t i = 0; path[i] != NullTerminator; ++i) {
  865. if (path[i] == AlternateDirectorySeparator) {
  866. *out = false;
  867. R_SUCCEED();
  868. }
  869. }
  870. }
  871. // Check that parent directory replacement is not needed if backslashes are allowed
  872. if (flags.IsBackslashAllowed() &&
  873. PathNormalizer::IsParentDirectoryPathReplacementNeeded(path)) {
  874. *out = false;
  875. R_SUCCEED();
  876. }
  877. // Check that the backslash state is valid
  878. bool is_backslash_contained = false;
  879. R_TRY(CheckInvalidBackslash(std::addressof(is_backslash_contained), path,
  880. flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed()));
  881. // Check that backslashes are contained only if allowed
  882. if (is_backslash_contained && !flags.IsBackslashAllowed()) {
  883. *out = false;
  884. R_SUCCEED();
  885. }
  886. // Check that the final result path is normalized
  887. size_t normal_len = 0;
  888. R_TRY(PathNormalizer::IsNormalized(out, std::addressof(normal_len), path,
  889. flags.IsAllCharactersAllowed()));
  890. // Add the normal length
  891. total_len += normal_len;
  892. // Set the output length
  893. *out_len = total_len;
  894. R_SUCCEED();
  895. }
  896. static constexpr Result Normalize(char* dst, size_t dst_size, const char* path, size_t path_len,
  897. const PathFlags& flags) {
  898. // Use StringTraits names for remainder of scope
  899. using namespace StringTraits;
  900. // Prepare to iterate
  901. const char* src = path;
  902. size_t cur_pos = 0;
  903. bool is_windows_path = false;
  904. // Check if the path is empty
  905. if (src[0] == NullTerminator) {
  906. if (dst_size != 0) {
  907. dst[0] = NullTerminator;
  908. }
  909. R_UNLESS(flags.IsEmptyPathAllowed(), ResultInvalidPathFormat);
  910. R_SUCCEED();
  911. }
  912. // Handle a mount name
  913. size_t mount_name_len = 0;
  914. if (flags.IsMountNameAllowed()) {
  915. R_TRY(ParseMountName(std::addressof(src), std::addressof(mount_name_len), dst + cur_pos,
  916. dst_size - cur_pos, src));
  917. cur_pos += mount_name_len;
  918. }
  919. // Handle a drive-relative prefix
  920. bool is_drive_relative = false;
  921. if (src[0] != DirectorySeparator && !IsPathStartWithCurrentDirectory(src) &&
  922. !IsWindowsPath(src, false)) {
  923. R_UNLESS(flags.IsRelativePathAllowed(), ResultInvalidPathFormat);
  924. R_UNLESS(!IsInvalidCharacter(src[0]), ResultInvalidPathFormat);
  925. dst[cur_pos++] = Dot;
  926. is_drive_relative = true;
  927. }
  928. size_t relative_len = 0;
  929. if (flags.IsRelativePathAllowed()) {
  930. R_UNLESS(cur_pos < dst_size, ResultTooLongPath);
  931. R_TRY(ParseRelativeDotPath(std::addressof(src), std::addressof(relative_len),
  932. dst + cur_pos, dst_size - cur_pos, src));
  933. cur_pos += relative_len;
  934. if (src[0] == NullTerminator) {
  935. R_UNLESS(cur_pos < dst_size, ResultTooLongPath);
  936. dst[cur_pos] = NullTerminator;
  937. R_SUCCEED();
  938. }
  939. }
  940. // Handle a windows path
  941. if (flags.IsWindowsPathAllowed()) {
  942. const char* const orig = src;
  943. R_UNLESS(cur_pos < dst_size, ResultTooLongPath);
  944. size_t windows_len = 0;
  945. R_TRY(ParseWindowsPath(std::addressof(src), std::addressof(windows_len), dst + cur_pos,
  946. dst_size - cur_pos, src, mount_name_len != 0));
  947. cur_pos += windows_len;
  948. if (src[0] == NullTerminator) {
  949. /* NOTE: Bug in original code here repeated, should be checking cur_pos + 2. */
  950. R_UNLESS(cur_pos + 1 < dst_size, ResultTooLongPath);
  951. dst[cur_pos + 0] = DirectorySeparator;
  952. dst[cur_pos + 1] = NullTerminator;
  953. R_SUCCEED();
  954. }
  955. if ((src - orig) > 0) {
  956. is_windows_path = true;
  957. }
  958. }
  959. // Check for invalid backslash
  960. bool backslash_contained = false;
  961. R_TRY(CheckInvalidBackslash(std::addressof(backslash_contained), src,
  962. flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed()));
  963. // Handle backslash replacement as necessary
  964. if (backslash_contained && flags.IsWindowsPathAllowed()) {
  965. // Create a temporary buffer holding a slash-replaced version of the path.
  966. // NOTE: Nintendo unnecessarily allocates and replaces here a fully copy of the path,
  967. // despite having skipped some of it already.
  968. const size_t replaced_src_len = path_len - (src - path);
  969. char* replaced_src = nullptr;
  970. SCOPE_EXIT {
  971. if (replaced_src != nullptr) {
  972. if (std::is_constant_evaluated()) {
  973. delete[] replaced_src;
  974. } else {
  975. Deallocate(replaced_src, replaced_src_len);
  976. }
  977. }
  978. };
  979. if (std::is_constant_evaluated()) {
  980. replaced_src = new char[replaced_src_len];
  981. } else {
  982. replaced_src = static_cast<char*>(Allocate(replaced_src_len));
  983. }
  984. Strlcpy<char>(replaced_src, src, replaced_src_len);
  985. Replace(replaced_src, replaced_src_len, AlternateDirectorySeparator,
  986. DirectorySeparator);
  987. size_t dummy;
  988. R_TRY(PathNormalizer::Normalize(dst + cur_pos, std::addressof(dummy), replaced_src,
  989. dst_size - cur_pos, is_windows_path, is_drive_relative,
  990. flags.IsAllCharactersAllowed()));
  991. } else {
  992. // We can just do normalization
  993. size_t dummy;
  994. R_TRY(PathNormalizer::Normalize(dst + cur_pos, std::addressof(dummy), src,
  995. dst_size - cur_pos, is_windows_path, is_drive_relative,
  996. flags.IsAllCharactersAllowed()));
  997. }
  998. R_SUCCEED();
  999. }
  1000. };
  1001. } // namespace FileSys