path.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const path = require("path");
  7. const CHAR_HASH = "#".charCodeAt(0);
  8. const CHAR_SLASH = "/".charCodeAt(0);
  9. const CHAR_BACKSLASH = "\\".charCodeAt(0);
  10. const CHAR_A = "A".charCodeAt(0);
  11. const CHAR_Z = "Z".charCodeAt(0);
  12. const CHAR_LOWER_A = "a".charCodeAt(0);
  13. const CHAR_LOWER_Z = "z".charCodeAt(0);
  14. const CHAR_DOT = ".".charCodeAt(0);
  15. const CHAR_COLON = ":".charCodeAt(0);
  16. const posixNormalize = path.posix.normalize;
  17. const winNormalize = path.win32.normalize;
  18. /**
  19. * @enum {number}
  20. */
  21. const PathType = Object.freeze({
  22. Empty: 0,
  23. Normal: 1,
  24. Relative: 2,
  25. AbsoluteWin: 3,
  26. AbsolutePosix: 4,
  27. Internal: 5,
  28. });
  29. module.exports.PathType = PathType;
  30. const invalidSegmentRegEx =
  31. /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))?(\\|\/|$)/i;
  32. module.exports.invalidSegmentRegEx = invalidSegmentRegEx;
  33. const deprecatedInvalidSegmentRegEx =
  34. /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\|\/|$)/i;
  35. module.exports.deprecatedInvalidSegmentRegEx = deprecatedInvalidSegmentRegEx;
  36. /**
  37. * @param {string} maybePath a path
  38. * @returns {PathType} type of path
  39. */
  40. const getType = (maybePath) => {
  41. switch (maybePath.length) {
  42. case 0:
  43. return PathType.Empty;
  44. case 1: {
  45. const c0 = maybePath.charCodeAt(0);
  46. switch (c0) {
  47. case CHAR_DOT:
  48. return PathType.Relative;
  49. case CHAR_SLASH:
  50. return PathType.AbsolutePosix;
  51. case CHAR_HASH:
  52. return PathType.Internal;
  53. }
  54. return PathType.Normal;
  55. }
  56. case 2: {
  57. const c0 = maybePath.charCodeAt(0);
  58. switch (c0) {
  59. case CHAR_DOT: {
  60. const c1 = maybePath.charCodeAt(1);
  61. switch (c1) {
  62. case CHAR_DOT:
  63. case CHAR_SLASH:
  64. return PathType.Relative;
  65. }
  66. return PathType.Normal;
  67. }
  68. case CHAR_SLASH:
  69. return PathType.AbsolutePosix;
  70. case CHAR_HASH:
  71. return PathType.Internal;
  72. }
  73. const c1 = maybePath.charCodeAt(1);
  74. if (
  75. c1 === CHAR_COLON &&
  76. ((c0 >= CHAR_A && c0 <= CHAR_Z) ||
  77. (c0 >= CHAR_LOWER_A && c0 <= CHAR_LOWER_Z))
  78. ) {
  79. return PathType.AbsoluteWin;
  80. }
  81. return PathType.Normal;
  82. }
  83. }
  84. const c0 = maybePath.charCodeAt(0);
  85. switch (c0) {
  86. case CHAR_DOT: {
  87. const c1 = maybePath.charCodeAt(1);
  88. switch (c1) {
  89. case CHAR_SLASH:
  90. return PathType.Relative;
  91. case CHAR_DOT: {
  92. const c2 = maybePath.charCodeAt(2);
  93. if (c2 === CHAR_SLASH) return PathType.Relative;
  94. return PathType.Normal;
  95. }
  96. }
  97. return PathType.Normal;
  98. }
  99. case CHAR_SLASH:
  100. return PathType.AbsolutePosix;
  101. case CHAR_HASH:
  102. return PathType.Internal;
  103. }
  104. const c1 = maybePath.charCodeAt(1);
  105. if (c1 === CHAR_COLON) {
  106. const c2 = maybePath.charCodeAt(2);
  107. if (
  108. (c2 === CHAR_BACKSLASH || c2 === CHAR_SLASH) &&
  109. ((c0 >= CHAR_A && c0 <= CHAR_Z) ||
  110. (c0 >= CHAR_LOWER_A && c0 <= CHAR_LOWER_Z))
  111. ) {
  112. return PathType.AbsoluteWin;
  113. }
  114. }
  115. return PathType.Normal;
  116. };
  117. module.exports.getType = getType;
  118. /**
  119. * @param {string} maybePath a path
  120. * @returns {string} the normalized path
  121. */
  122. const normalize = (maybePath) => {
  123. switch (getType(maybePath)) {
  124. case PathType.Empty:
  125. return maybePath;
  126. case PathType.AbsoluteWin:
  127. return winNormalize(maybePath);
  128. case PathType.Relative: {
  129. const r = posixNormalize(maybePath);
  130. return getType(r) === PathType.Relative ? r : `./${r}`;
  131. }
  132. }
  133. return posixNormalize(maybePath);
  134. };
  135. module.exports.normalize = normalize;
  136. /**
  137. * @param {string} rootPath the root path
  138. * @param {string | undefined} request the request path
  139. * @returns {string} the joined path
  140. */
  141. const join = (rootPath, request) => {
  142. if (!request) return normalize(rootPath);
  143. const requestType = getType(request);
  144. switch (requestType) {
  145. case PathType.AbsolutePosix:
  146. return posixNormalize(request);
  147. case PathType.AbsoluteWin:
  148. return winNormalize(request);
  149. }
  150. switch (getType(rootPath)) {
  151. case PathType.Normal:
  152. case PathType.Relative:
  153. case PathType.AbsolutePosix:
  154. return posixNormalize(`${rootPath}/${request}`);
  155. case PathType.AbsoluteWin:
  156. return winNormalize(`${rootPath}\\${request}`);
  157. }
  158. switch (requestType) {
  159. case PathType.Empty:
  160. return rootPath;
  161. case PathType.Relative: {
  162. const r = posixNormalize(rootPath);
  163. return getType(r) === PathType.Relative ? r : `./${r}`;
  164. }
  165. }
  166. return posixNormalize(rootPath);
  167. };
  168. module.exports.join = join;
  169. /** @type {Map<string, Map<string, string | undefined>>} */
  170. const joinCache = new Map();
  171. /**
  172. * @param {string} rootPath the root path
  173. * @param {string} request the request path
  174. * @returns {string} the joined path
  175. */
  176. const cachedJoin = (rootPath, request) => {
  177. /** @type {string | undefined} */
  178. let cacheEntry;
  179. let cache = joinCache.get(rootPath);
  180. if (cache === undefined) {
  181. joinCache.set(rootPath, (cache = new Map()));
  182. } else {
  183. cacheEntry = cache.get(request);
  184. if (cacheEntry !== undefined) return cacheEntry;
  185. }
  186. cacheEntry = join(rootPath, request);
  187. cache.set(request, cacheEntry);
  188. return cacheEntry;
  189. };
  190. module.exports.cachedJoin = cachedJoin;