2
0

Path.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971
  1. #include "Path.h"
  2. #include "Logging.h"
  3. #include "Unicode.h"
  4. #if GP_PLATFORM_WINDOWS
  5. # include <pathcch.h>
  6. #endif
  7. #include <functional>
  8. #include <utility>
  9. #include <vector>
  10. namespace gameplay
  11. {
  12. Path::Path()
  13. {
  14. }
  15. Path::Path(const char* path, size_t pathLen)
  16. {
  17. if (path && pathLen)
  18. {
  19. _pathStr.assign(path, pathLen);
  20. }
  21. _sanitize_path();
  22. }
  23. Path::Path(const char* path)
  24. {
  25. if (path)
  26. {
  27. _pathStr = path;
  28. }
  29. _sanitize_path();
  30. }
  31. Path::Path(std::string path) :
  32. _pathStr(std::move(path))
  33. {
  34. _sanitize_path();
  35. }
  36. Path::Path(const Path& other) :
  37. _pathStr(other._pathStr)
  38. {
  39. }
  40. Path::Path(Path&& other) noexcept :
  41. _pathStr(std::move(other._pathStr))
  42. {
  43. }
  44. Path::~Path()
  45. {
  46. }
  47. Path& Path::operator=(const Path& other)
  48. {
  49. _pathStr = other._pathStr;
  50. return *this;
  51. }
  52. Path& Path::operator=(Path&& other) noexcept
  53. {
  54. _pathStr = std::move(other._pathStr);
  55. return *this;
  56. }
  57. std::string Path::get_string() const
  58. {
  59. return _pathStr;
  60. }
  61. Path::operator std::string() const
  62. {
  63. return _pathStr;
  64. }
  65. const char* Path::c_str() const
  66. {
  67. return _pathStr.c_str();
  68. }
  69. bool Path::operator==(const Path& other) const
  70. {
  71. return _pathStr == other._pathStr;
  72. }
  73. bool Path::operator==(const std::string& other) const
  74. {
  75. return _pathStr == other;
  76. }
  77. bool Path::operator==(const char* other) const
  78. {
  79. if (other == nullptr)
  80. {
  81. return false;
  82. }
  83. return _pathStr == other;
  84. }
  85. bool Path::operator!=(const Path& other) const
  86. {
  87. return !(*this == other);
  88. }
  89. bool Path::operator!=(const std::string& other) const
  90. {
  91. return !(*this == other);
  92. }
  93. bool Path::operator!=(const char* other) const
  94. {
  95. return !(*this == other);
  96. }
  97. size_t Path::len() const
  98. {
  99. return _pathStr.size();
  100. }
  101. Path& Path::clear()
  102. {
  103. _pathStr.clear();
  104. return *this;
  105. }
  106. bool Path::is_empty() const
  107. {
  108. return _pathStr.empty();
  109. }
  110. Path Path::get_filename() const
  111. {
  112. const char* filenamePtr = _get_filename_ptr();
  113. if (!filenamePtr)
  114. {
  115. return Path();
  116. }
  117. const size_t filenameOffset = filenamePtr - _pathStr.data();
  118. return Path(_pathStr.substr(filenameOffset, _pathStr.size() - filenameOffset));
  119. }
  120. Path Path::get_extension() const
  121. {
  122. const char* extPtr = _get_extension_ptr();
  123. if (!extPtr)
  124. {
  125. return Path();
  126. }
  127. const size_t extOffset = extPtr - _pathStr.data();
  128. return Path(_pathStr.substr(extOffset, _pathStr.size() - extOffset));
  129. }
  130. Path Path::get_parent() const
  131. {
  132. const char* parentPathEndPtr = _get_filename_ptr();
  133. const char* pathDataStart = _pathStr.data();
  134. if (parentPathEndPtr == nullptr)
  135. {
  136. parentPathEndPtr = pathDataStart + _pathStr.size();
  137. }
  138. // Cleaning up the trailing slashes;
  139. while (parentPathEndPtr > pathDataStart && parentPathEndPtr[-1] == FORWARD_SLASH_CHAR)
  140. {
  141. --parentPathEndPtr;
  142. }
  143. if (parentPathEndPtr == pathDataStart)
  144. {
  145. return Path();
  146. }
  147. return Path(_pathStr.substr(0, parentPathEndPtr - pathDataStart));
  148. }
  149. Path Path::get_stem() const
  150. {
  151. const char* extPtr = _get_extension_ptr();
  152. if (extPtr == nullptr)
  153. {
  154. return get_filename();
  155. }
  156. const char* filenamePtr = _get_filename_ptr();
  157. return Path(_pathStr.substr(filenamePtr - _pathStr.data(), extPtr - filenamePtr));
  158. }
  159. Path Path::get_root_name() const
  160. {
  161. const char* rootNameEndPtr = _get_root_name_end_ptr();
  162. if (!rootNameEndPtr)
  163. {
  164. return Path();
  165. }
  166. return Path(_pathStr.substr(0, rootNameEndPtr - _pathStr.data()));
  167. }
  168. Path Path::get_relative_part() const
  169. {
  170. const char* relativePartPtr = _get_relative_part_ptr();
  171. if (relativePartPtr == nullptr)
  172. {
  173. return Path();
  174. }
  175. const size_t relativePartOffset = relativePartPtr - _pathStr.data();
  176. return Path(_pathStr.substr(relativePartOffset, _pathStr.size() - relativePartOffset));
  177. }
  178. Path Path::get_root_directory() const
  179. {
  180. const char* rootDirectoryEndPtr = _get_root_directory_end_ptr();
  181. if (!rootDirectoryEndPtr)
  182. {
  183. return Path();
  184. }
  185. const char* rootNameEndPtr = _get_root_name_end_ptr();
  186. return Path(rootDirectoryEndPtr == rootNameEndPtr ? EMPTY_STRING : FORWARD_SLASH_STRING);
  187. }
  188. bool Path::has_root_directory() const noexcept
  189. {
  190. return !get_root_directory().is_empty();
  191. }
  192. Path Path::get_root() const
  193. {
  194. const char* rootDirectoryEndPtr = _get_root_directory_end_ptr();
  195. if (!rootDirectoryEndPtr)
  196. {
  197. return Path();
  198. }
  199. return Path(_pathStr.substr(0, rootDirectoryEndPtr - _pathStr.data()));
  200. }
  201. Path Path::concat(const Path& concatedPart) const
  202. {
  203. if (is_empty())
  204. {
  205. return concatedPart;
  206. }
  207. if (concatedPart.is_empty())
  208. {
  209. return *this;
  210. }
  211. PathPartDesc parts[] = { { c_str(), len() },
  212. { concatedPart.c_str(), concatedPart.len() } };
  213. return _concat(parts, GP_COUNTOF(parts));
  214. }
  215. Path& Path::operator/=(const Path& path)
  216. {
  217. return *this = *this / path;
  218. }
  219. Path& Path::operator+=(const Path& path)
  220. {
  221. return *this = *this + path;
  222. }
  223. Path& Path::replace_extension(const Path& newExtension)
  224. {
  225. const char* extPtr = _get_extension_ptr();
  226. // check if we need to just remove the extension
  227. if (newExtension.is_empty())
  228. {
  229. if (extPtr)
  230. {
  231. _pathStr = _pathStr.substr(0, extPtr - _pathStr.data());
  232. }
  233. return *this;
  234. }
  235. const char* newExtensionData = newExtension.c_str();
  236. size_t newExtensionSize = newExtension.len();
  237. if (*newExtensionData == DOT_CHAR)
  238. {
  239. ++newExtensionData;
  240. --newExtensionSize;
  241. }
  242. size_t remainingPathSize = len();
  243. if (extPtr)
  244. {
  245. remainingPathSize = extPtr - _pathStr.data();
  246. size_t oldExtensionSize = len() - (extPtr - _pathStr.data());
  247. // skipping starting dot
  248. --oldExtensionSize;
  249. // checking for trying to use the same extension
  250. if (oldExtensionSize == newExtensionSize && ::memcmp(extPtr + 1, newExtensionData, newExtensionSize) == 0)
  251. {
  252. return *this;
  253. }
  254. }
  255. PathPartDesc parts[] = { { this->c_str(), remainingPathSize },
  256. { DOT_STRING, DOT_STRING_LENGTH },
  257. { newExtensionData, newExtensionSize } };
  258. return *this = _concat(parts, GP_COUNTOF(parts));
  259. }
  260. Path Path::get_absolute(const Path& root) const
  261. {
  262. if (is_absolute() || root.is_empty())
  263. {
  264. return this->get_normalized();
  265. }
  266. return root.join(*this).get_normalized();
  267. }
  268. bool Path::is_absolute() const
  269. {
  270. #if GP_PLATFORM_LINUX
  271. return !is_empty() && _pathStr[0] == FORWARD_SLASH_CHAR;
  272. #elif GP_PLATFORM_WINDOWS
  273. // drive root (D:/abc) case.
  274. // the only position where ':' is allowed on Windows.
  275. // check for separator is important, because D:temp.txt is a relative path on windows.
  276. const char* pathDataStart = _pathStr.data();
  277. const size_t pathDataLength = len();
  278. if (pathDataLength > 2 && pathDataStart[1] == COLON_CHAR && pathDataStart[2] == FORWARD_SLASH_CHAR)
  279. {
  280. return true;
  281. }
  282. // drive letter (D:) case
  283. if (pathDataLength == 2 && pathDataStart[1] == COLON_CHAR)
  284. {
  285. return true;
  286. }
  287. // extended drive letter path (ie: prefixed with "//./D:").
  288. if (pathDataLength > 4 && pathDataStart[0] == FORWARD_SLASH_CHAR && pathDataStart[1] == FORWARD_SLASH_CHAR &&
  289. pathDataStart[2] == DOT_CHAR && pathDataStart[3] == FORWARD_SLASH_CHAR)
  290. {
  291. // at least a drive name was specified.
  292. if (pathDataLength > 6 && pathDataStart[5] == COLON_CHAR)
  293. {
  294. // drive plus an absolute path was specified (ie: "//./d:/abc") => succeed.
  295. if (pathDataStart[6] == FORWARD_SLASH_CHAR)
  296. {
  297. return true;
  298. }
  299. // drive and relative path was specified (ie: "//./d:temp.txt") => fail. We need to
  300. // specifically fail here because this path would also get picked up by the generic
  301. // special path check below and report success erroneously.
  302. else
  303. {
  304. return false;
  305. }
  306. }
  307. // requesting the full drive volume (ie: "//./d:") => report absolute to match behaviour
  308. // in the "d:" case above.
  309. if (pathDataLength == 6 && pathDataStart[5] == COLON_CHAR)
  310. {
  311. return true;
  312. }
  313. }
  314. // check for special paths. this includes all windows paths that begin with "\\" (converted
  315. // to unix path separators for our purposes). this class of paths includes extended path
  316. // names (ie: prefixed with "\\?\"), device names (ie: prefixed with "\\.\"), physical drive
  317. // paths (ie: prefixed with "\\.\PhysicalDrive<n>"), removeable media access (ie: "\\.\X:")
  318. // COM ports (ie: "\\.\COM*"), and UNC paths (ie: prefixed with "\\servername\sharename\").
  319. //
  320. // note: that it is not necessarily sufficient to get absolute vs relative based solely on
  321. // the "//" prefix here without needing to dig further into the specific name used and what
  322. // it actually represents. for now, we'll just assume that device, drive, volume, and
  323. // port names will not be used here and treat it as a UNC path. since all extended paths
  324. // and UNC paths must always be absolute, this should hold up at least for those. If a
  325. // path for a drive, volume, or device is actually passed in here, it will still be treated
  326. // as though it were an absolute path. the results of using such a path further may be
  327. // undefined however.
  328. if (pathDataLength > 2 &&
  329. pathDataStart[0] == FORWARD_SLASH_CHAR &&
  330. pathDataStart[1] == FORWARD_SLASH_CHAR &&
  331. pathDataStart[2] != FORWARD_SLASH_CHAR)
  332. {
  333. return true;
  334. }
  335. return false;
  336. #endif
  337. }
  338. Path Path::get_normalized() const
  339. {
  340. if (is_empty())
  341. {
  342. return Path();
  343. }
  344. constexpr size_t kDefaultTokenCount = 128;
  345. enum class NormalizePartType
  346. {
  347. SLASH,
  348. ROOT_NAME,
  349. ROOT_SLASH,
  350. DOT,
  351. DOTDOT,
  352. NAME,
  353. ERR
  354. };
  355. struct ParsedPathPartDesc : PathPartDesc
  356. {
  357. NormalizePartType type;
  358. ParsedPathPartDesc(const char* partData, size_t partSize, PathTokenType partType)
  359. : PathPartDesc{ partData, partSize }
  360. {
  361. switch (partType)
  362. {
  363. case PathTokenType::SLASH:
  364. type = NormalizePartType::SLASH;
  365. break;
  366. case PathTokenType::ROOT_NAME:
  367. type = NormalizePartType::ROOT_NAME;
  368. break;
  369. case PathTokenType::DOT:
  370. type = NormalizePartType::DOT;
  371. break;
  372. case PathTokenType::DOTDOT:
  373. type = NormalizePartType::DOTDOT;
  374. break;
  375. case PathTokenType::NAME:
  376. type = NormalizePartType::NAME;
  377. break;
  378. default:
  379. type = NormalizePartType::ERR;
  380. GP_LOG_ERROR("Invalid internal token state while normalizing a path.");
  381. GP_ASSERT(false);
  382. break;
  383. }
  384. }
  385. ParsedPathPartDesc(const char* partData, size_t partSize, NormalizePartType partType)
  386. : PathPartDesc{ partData, partSize }, type(partType)
  387. {
  388. }
  389. };
  390. std::vector<ParsedPathPartDesc> resultPathTokens;
  391. resultPathTokens.reserve(kDefaultTokenCount);
  392. const char* prevTokenEnd = _get_root_directory_end_ptr();
  393. const char* pathDataStart = _pathStr.data();
  394. const size_t pathDataLength = len();
  395. if (prevTokenEnd && prevTokenEnd > pathDataStart)
  396. {
  397. // add the root name and the root directory as different elements
  398. const char* possibleSlashPos = prevTokenEnd - 1;
  399. if (*possibleSlashPos == FORWARD_SLASH_CHAR)
  400. {
  401. if (possibleSlashPos > pathDataStart)
  402. {
  403. resultPathTokens.emplace_back(
  404. pathDataStart, static_cast<size_t>(possibleSlashPos - pathDataStart), PathTokenType::ROOT_NAME);
  405. }
  406. resultPathTokens.emplace_back(FORWARD_SLASH_STRING, FORWARD_SLASH_STRING_LENGTH, NormalizePartType::ROOT_SLASH);
  407. }
  408. else
  409. {
  410. resultPathTokens.emplace_back(
  411. pathDataStart, static_cast<size_t>(prevTokenEnd - pathDataStart), PathTokenType::ROOT_NAME);
  412. }
  413. }
  414. else
  415. {
  416. prevTokenEnd = pathDataStart;
  417. }
  418. bool alreadyNormalized = true;
  419. const char* bufferEnd = pathDataStart + pathDataLength;
  420. PathTokenType curTokenType = PathTokenType::NAME;
  421. for (const char* curTokenEnd = _get_token_end(prevTokenEnd, bufferEnd, curTokenType); curTokenEnd != nullptr;
  422. prevTokenEnd = curTokenEnd, curTokenEnd = _get_token_end(prevTokenEnd, bufferEnd, curTokenType))
  423. {
  424. switch (curTokenType)
  425. {
  426. case PathTokenType::SLASH:
  427. if (resultPathTokens.empty() || resultPathTokens.back().type == NormalizePartType::SLASH ||
  428. resultPathTokens.back().type == NormalizePartType::ROOT_SLASH)
  429. {
  430. // skip if we already have a slash at the end
  431. alreadyNormalized = false;
  432. continue;
  433. }
  434. break;
  435. case PathTokenType::DOT:
  436. // skip it
  437. alreadyNormalized = false;
  438. continue;
  439. case PathTokenType::DOTDOT:
  440. if (resultPathTokens.empty())
  441. {
  442. break;
  443. }
  444. // check if the previous element is a part of the root name (even without a slash)
  445. // and skip dot-dot in such case
  446. if (resultPathTokens.back().type == NormalizePartType::ROOT_NAME ||
  447. resultPathTokens.back().type == NormalizePartType::ROOT_SLASH)
  448. {
  449. alreadyNormalized = false;
  450. continue;
  451. }
  452. if (resultPathTokens.size() > 1)
  453. {
  454. GP_ASSERT(resultPathTokens.back().type == NormalizePartType::SLASH);
  455. const NormalizePartType tokenTypeBeforeSlash = resultPathTokens[resultPathTokens.size() - 2].type;
  456. // remove <name>/<dot-dot> pattern
  457. if (tokenTypeBeforeSlash == NormalizePartType::NAME)
  458. {
  459. // remove the last slash
  460. resultPathTokens.pop_back();
  461. // remove the last named token
  462. resultPathTokens.pop_back();
  463. alreadyNormalized = false;
  464. // skip the addition of the dot-dot
  465. continue;
  466. }
  467. }
  468. break;
  469. case PathTokenType::NAME:
  470. // no special processing needed
  471. break;
  472. default:
  473. GP_LOG_ERROR("Invalid internal state while normalizing the path {%s}", c_str());
  474. GP_ASSERT(false);
  475. alreadyNormalized = false;
  476. continue;
  477. }
  478. resultPathTokens.emplace_back(prevTokenEnd, static_cast<size_t>(curTokenEnd - prevTokenEnd), curTokenType);
  479. }
  480. if (resultPathTokens.empty())
  481. {
  482. return Path(DOT_STRING);
  483. }
  484. else if (resultPathTokens.back().type == NormalizePartType::SLASH && resultPathTokens.size() > 1)
  485. {
  486. // remove the trailing slash for special cases like "./" and "../"
  487. const size_t indexOfTokenBeforeSlash = resultPathTokens.size() - 2;
  488. const NormalizePartType typeOfTokenBeforeSlash = resultPathTokens[indexOfTokenBeforeSlash].type;
  489. if (typeOfTokenBeforeSlash == NormalizePartType::DOT || typeOfTokenBeforeSlash == NormalizePartType::DOTDOT)
  490. {
  491. resultPathTokens.pop_back();
  492. alreadyNormalized = false;
  493. }
  494. }
  495. if (alreadyNormalized)
  496. {
  497. return *this;
  498. }
  499. std::vector<PathPartDesc> partsToJoin;
  500. partsToJoin.reserve(resultPathTokens.size());
  501. for (const auto& curTokenInfo : resultPathTokens)
  502. {
  503. partsToJoin.emplace_back(PathPartDesc{ curTokenInfo.data, curTokenInfo.size });
  504. }
  505. return _concat(partsToJoin.data(), partsToJoin.size());
  506. }
  507. Path& Path::normalize()
  508. {
  509. return *this = get_normalized();
  510. }
  511. Path Path::get_relative(const Path& base) const noexcept
  512. {
  513. // check if the operation is possible
  514. if (get_root_name() != base.get_root_name() ||
  515. is_absolute() != base.is_absolute() ||
  516. (!has_root_directory() && base.has_root_directory()))
  517. {
  518. return Path();
  519. }
  520. PathTokenType curPathTokenType = PathTokenType::ROOT_NAME;
  521. const char* curPathTokenEnd = _get_root_directory_end_ptr();
  522. const char* curPathTokenStart = curPathTokenEnd;
  523. const char* curPathEnd = _pathStr.data() + _pathStr.length();
  524. PathTokenType basePathTokenType = PathTokenType::ROOT_NAME;
  525. const char* basePathTokenEnd = base._get_root_directory_end_ptr();
  526. const char* basePathEnd = base._pathStr.data() + base._pathStr.length();
  527. // find the first mismatch
  528. for (;;)
  529. {
  530. curPathTokenStart = curPathTokenEnd;
  531. curPathTokenEnd = _get_token_end(curPathTokenEnd, curPathEnd, curPathTokenType);
  532. const char* baseTokenStart = basePathTokenEnd;
  533. basePathTokenEnd = _get_token_end(basePathTokenEnd, basePathEnd, basePathTokenType);
  534. if (!curPathTokenEnd || !basePathTokenEnd)
  535. {
  536. // check if both are null
  537. if (curPathTokenEnd == basePathTokenEnd)
  538. {
  539. return Path(DOT_STRING);
  540. }
  541. break;
  542. }
  543. if (curPathTokenType != basePathTokenType ||
  544. !std::equal(curPathTokenStart, curPathTokenEnd, baseTokenStart, basePathTokenEnd))
  545. {
  546. break;
  547. }
  548. }
  549. int requiredDotDotCount = 0;
  550. while (basePathTokenEnd)
  551. {
  552. if (basePathTokenType == PathTokenType::DOTDOT)
  553. {
  554. --requiredDotDotCount;
  555. }
  556. else if (basePathTokenType == PathTokenType::NAME)
  557. {
  558. ++requiredDotDotCount;
  559. }
  560. basePathTokenEnd = _get_token_end(basePathTokenEnd, basePathEnd, basePathTokenType);
  561. }
  562. if (requiredDotDotCount < 0)
  563. {
  564. return Path();
  565. }
  566. if (requiredDotDotCount == 0 && !curPathTokenEnd)
  567. {
  568. return Path(DOT_STRING);
  569. }
  570. const size_t leftoverCurPathSymbols = curPathTokenEnd != nullptr ? curPathEnd - curPathTokenStart : 0;
  571. const size_t requiredResultSize =
  572. (FORWARD_SLASH_STRING_LENGTH + DOTDOT_STRING_LENGTH) * requiredDotDotCount + leftoverCurPathSymbols;
  573. Path result;
  574. result._pathStr.reserve(requiredResultSize);
  575. if (requiredDotDotCount > 0)
  576. {
  577. result._pathStr += DOTDOT_STRING;
  578. --requiredDotDotCount;
  579. for (int i = 0; i < requiredDotDotCount; ++i)
  580. {
  581. result._pathStr += FORWARD_SLASH_CHAR;
  582. result._pathStr += DOTDOT_STRING;
  583. }
  584. }
  585. bool needsSeparator = !result._pathStr.empty();
  586. while (curPathTokenEnd)
  587. {
  588. if (curPathTokenType != PathTokenType::SLASH)
  589. {
  590. if (needsSeparator)
  591. {
  592. result._pathStr += FORWARD_SLASH_CHAR;
  593. }
  594. else
  595. {
  596. needsSeparator = true;
  597. }
  598. result._pathStr.append(curPathTokenStart, curPathTokenEnd - curPathTokenStart);
  599. }
  600. curPathTokenStart = curPathTokenEnd;
  601. curPathTokenEnd = _get_token_end(curPathTokenEnd, curPathEnd, curPathTokenType);
  602. }
  603. return result;
  604. }
  605. bool Path::is_relative() const
  606. {
  607. return !is_absolute();
  608. }
  609. Path Path::join(const Path& joinedPart) const
  610. {
  611. if (is_empty())
  612. {
  613. return joinedPart;
  614. }
  615. if (joinedPart.is_empty())
  616. {
  617. return *this;
  618. }
  619. const bool haveSeparator =
  620. _pathStr.back() == FORWARD_SLASH_CHAR || joinedPart._pathStr.front() == FORWARD_SLASH_CHAR;
  621. PathPartDesc parts[3] = {};
  622. size_t numParts = 0;
  623. parts[numParts++] = { c_str(), len() };
  624. if (!haveSeparator)
  625. {
  626. parts[numParts++] = { FORWARD_SLASH_STRING, FORWARD_SLASH_STRING_LENGTH };
  627. }
  628. parts[numParts++] = { joinedPart.c_str(), joinedPart.len() };
  629. return _concat(parts, numParts);
  630. }
  631. #if GP_PLATFORM_WINDOWS
  632. std::wstring Path::convert_utf8_to_windows_path(const std::string& path)
  633. {
  634. std::wstring pathW = Unicode::convert_utf8_to_wide(path);
  635. if (pathW == Unicode::UTF8_TO_WIDE_FAILURE)
  636. {
  637. return L"";
  638. }
  639. std::replace(pathW.begin(), pathW.end(), L'/', L'\\');
  640. bool hasPrefix = (pathW.compare(0, 4, L"\\\\?\\") == 0);
  641. if (pathW.size() >= MAX_PATH && !hasPrefix)
  642. {
  643. return L"\\\\?\\" + pathW;
  644. }
  645. if (pathW.size() < MAX_PATH && hasPrefix)
  646. {
  647. return pathW.substr(4, pathW.size() - 4);
  648. }
  649. return pathW;
  650. }
  651. std::string Path::convert_windows_to_utf8_path(const std::wstring& pathW)
  652. {
  653. bool hasPrefix = (pathW.compare(0, 4, L"\\\\?\\") == 0);
  654. std::string path = Unicode::convert_wide_to_utf8(pathW.c_str() + (hasPrefix ? 4 : 0));
  655. if (path == Unicode::WIDE_TO_UTF8_FAILURE)
  656. {
  657. return "";
  658. }
  659. std::replace(path.begin(), path.end(), '\\', '/');
  660. return path;
  661. }
  662. std::wstring Path::get_windows_canonical_path(const std::wstring& path)
  663. {
  664. wchar_t* canonical = nullptr;
  665. if (::PathAllocCanonicalize(path.c_str(), PATHCCH_ALLOW_LONG_PATHS, &canonical) == S_OK)
  666. {
  667. std::wstring result = canonical;
  668. LocalFree(canonical);
  669. return result;
  670. }
  671. GP_LOG_WARN("The path '%s' could not be canonicalized!", Path::convert_windows_to_utf8_path(path).c_str());
  672. return path;
  673. }
  674. std::wstring Path::get_windows_full_path(const std::wstring& path)
  675. {
  676. DWORD size = ::GetFullPathNameW(path.c_str(), 0, nullptr, nullptr);
  677. if (size != 0)
  678. {
  679. std::wstring fullPathName;
  680. fullPathName.resize(size - 1);
  681. if (::GetFullPathNameW(path.c_str(), size, &fullPathName[0], nullptr) != 0)
  682. {
  683. return fullPathName;
  684. }
  685. }
  686. GP_LOG_WARN("Can't retrieve the full path of '%s'!", Path::convert_windows_to_utf8_path(path).c_str());
  687. return path;
  688. }
  689. std::wstring Path::fix_windows_path_prefixes(const std::wstring& path)
  690. {
  691. bool hasPrefix = (path.compare(0, 4, L"\\\\?\\") == 0);
  692. if (path.size() >= MAX_PATH && !hasPrefix)
  693. {
  694. return L"\\\\?\\" + path;
  695. }
  696. if (path.size() < MAX_PATH && hasPrefix)
  697. {
  698. return path.substr(4, path.size() - 4);
  699. }
  700. return path;
  701. }
  702. #endif
  703. const char* Path::_get_token_end(const char* bufferBegin, const char* bufferEnd, PathTokenType& resultType)
  704. {
  705. /**
  706. * Gets the next path token starting at bufferStart till bufferEnd (points after the end of the
  707. * buffer data). On success returns pointer immediately after the token data and returns token type in the
  708. * resultType. On failure returns null and the value of the resultType is undetermined.
  709. * Note: it doesn't determine if a NAME is a ROOT_NAME. (ROOT_NAME is added to enum for convenience)
  710. */
  711. if (bufferBegin == nullptr || bufferEnd == nullptr || bufferEnd <= bufferBegin)
  712. {
  713. return nullptr;
  714. }
  715. // trying to find the next slash
  716. const char* tokenEnd = _find_from_start(bufferBegin, bufferEnd - bufferBegin, FORWARD_SLASH_CHAR);
  717. // if found a slash as the first symbol then return pointer to the data after it
  718. if (tokenEnd == bufferBegin)
  719. {
  720. resultType = PathTokenType::SLASH;
  721. return tokenEnd + 1;
  722. }
  723. // if no slash found we consider all passed data as a single token
  724. if (!tokenEnd)
  725. {
  726. tokenEnd = bufferEnd;
  727. }
  728. const size_t tokenSize = tokenEnd - bufferBegin;
  729. if (tokenSize == 1 && *bufferBegin == DOT_CHAR)
  730. {
  731. resultType = PathTokenType::DOT;
  732. }
  733. else if (tokenSize == 2 && bufferBegin[0] == DOT_CHAR && bufferBegin[1] == DOT_CHAR)
  734. {
  735. resultType = PathTokenType::DOTDOT;
  736. }
  737. else
  738. {
  739. resultType = PathTokenType::NAME;
  740. }
  741. return tokenEnd;
  742. }
  743. Path Path::_concat(const PathPartDesc* pathParts, size_t numParts)
  744. {
  745. if (!pathParts || numParts == 0)
  746. {
  747. return Path();
  748. }
  749. size_t totalSize = 0;
  750. for (size_t i = 0; i < numParts; ++i)
  751. {
  752. if (pathParts[i].data)
  753. {
  754. totalSize += pathParts[i].size;
  755. }
  756. }
  757. if (totalSize == 0)
  758. {
  759. return Path();
  760. }
  761. std::string buffer;
  762. buffer.reserve(totalSize);
  763. for (size_t i = 0; i < numParts; ++i)
  764. {
  765. const char* partData = pathParts[i].data;
  766. const size_t partSize = pathParts[i].size;
  767. if (partData && partSize > 0)
  768. {
  769. buffer.append(partData, partSize);
  770. }
  771. }
  772. return Path(std::move(buffer));
  773. }
  774. const char* Path::_get_filename_ptr() const
  775. {
  776. if (is_empty())
  777. {
  778. return nullptr;
  779. }
  780. const char* pathDataStart = _pathStr.data();
  781. const size_t pathDataSize = _pathStr.size();
  782. // find the last slash
  783. const char* slashPtr = _find_from_end(pathDataStart, pathDataSize, FORWARD_SLASH_CHAR);
  784. // oo slash == only filename
  785. if (!slashPtr)
  786. {
  787. return pathDataStart;
  788. }
  789. const char* filenamePtr = slashPtr + 1;
  790. // check that there is any data after the last slash
  791. if (filenamePtr >= pathDataStart + pathDataSize)
  792. {
  793. return nullptr;
  794. }
  795. return filenamePtr;
  796. }
  797. const char* Path::_get_extension_ptr() const
  798. {
  799. const char* filenamePtr = _get_filename_ptr();
  800. if (filenamePtr == nullptr)
  801. {
  802. return nullptr;
  803. }
  804. const char* pathDataStart = _pathStr.data();
  805. const size_t pathDataSize = _pathStr.size();
  806. const size_t filenameOffset = filenamePtr - pathDataStart;
  807. const char* extStart = _find_from_end(filenamePtr, pathDataSize - filenameOffset, DOT_CHAR);
  808. // checking if the pointer is at the last position (i.e. filename ends with just a dot(s))
  809. if (extStart == pathDataStart + pathDataSize - 1)
  810. {
  811. extStart = nullptr;
  812. }
  813. return extStart != filenamePtr ? extStart : nullptr;
  814. }
  815. const char* Path::_get_root_name_end_ptr() const
  816. {
  817. if (is_empty())
  818. {
  819. return nullptr;
  820. }
  821. const char* pathDataStart = _pathStr.data();
  822. const size_t pathDataSize = _pathStr.size();
  823. if (pathDataSize < 2)
  824. {
  825. return pathDataStart;
  826. }
  827. #if GP_PLATFORM_WINDOWS
  828. // check if the path starts with a drive letter followed by a colon (ex: "A:/...")
  829. if (pathDataStart[1] == COLON_CHAR)
  830. {
  831. const char firstLetter = static_cast<char>(std::tolower(pathDataStart[0]));
  832. if (FIRST_LOWERCASE_LETTER <= firstLetter && firstLetter <= LAST_LOWERCASE_LETTER)
  833. {
  834. return pathDataStart + 2;
  835. }
  836. }
  837. #endif
  838. // check if it's a UNC name ("//location/...")
  839. // note: just checking if the first 2 chars are forward slashes and the third symbol is not
  840. if (pathDataSize >= 3 && pathDataStart[0] == FORWARD_SLASH_CHAR && pathDataStart[1] == FORWARD_SLASH_CHAR &&
  841. pathDataStart[2] != FORWARD_SLASH_CHAR)
  842. {
  843. // search for a root directory
  844. const char* const slashPtr = _find_from_start(pathDataStart + 3, pathDataSize - 3, FORWARD_SLASH_CHAR);
  845. return slashPtr ? slashPtr : pathDataStart + pathDataSize;
  846. }
  847. return pathDataStart;
  848. }
  849. const char* Path::_get_relative_part_ptr() const
  850. {
  851. const char* rootNameEndPtr = _get_root_name_end_ptr();
  852. // the rootNameEndPtr is always not null if the path is not empty
  853. if (!rootNameEndPtr)
  854. {
  855. return nullptr;
  856. }
  857. const size_t rootEndOffset = rootNameEndPtr - _pathStr.data();
  858. // find the pointer to the first symbol after the root name that is not a forward slash
  859. return _find_from_start<std::not_equal_to<char>>(
  860. rootNameEndPtr, _pathStr.size() - rootEndOffset, FORWARD_SLASH_CHAR);
  861. }
  862. const char* Path::_get_root_directory_end_ptr() const
  863. {
  864. const char* rootNameEndPtr = _get_root_name_end_ptr();
  865. const char* relativePartPtr = _get_relative_part_ptr();
  866. if (relativePartPtr != rootNameEndPtr)
  867. {
  868. const char* result = rootNameEndPtr + 1;
  869. if (result > _pathStr.data() + _pathStr.size())
  870. {
  871. result = rootNameEndPtr;
  872. }
  873. return result;
  874. }
  875. return rootNameEndPtr;
  876. }
  877. void Path::_sanitize_path()
  878. {
  879. #if GP_PLATFORM_WINDOWS
  880. // change the backward slashes for Windows to forward ones
  881. for (auto& curChar : _pathStr)
  882. {
  883. if (curChar == BACKWARD_SLASH_CHAR)
  884. {
  885. curChar = FORWARD_SLASH_CHAR;
  886. }
  887. }
  888. #elif GP_PLATFORM_LINUX
  889. // no change needed
  890. #endif
  891. }
  892. }