url.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. "use strict";
  2. let isWindows = /^win/.test(process.platform),
  3. forwardSlashPattern = /\//g,
  4. protocolPattern = /^(\w{2,}):\/\//i,
  5. url = module.exports,
  6. jsonPointerSlash = /~1/g,
  7. jsonPointerTilde = /~0/g;
  8. // RegExp patterns to URL-encode special characters in local filesystem paths
  9. let urlEncodePatterns = [
  10. /\?/g, "%3F",
  11. /\#/g, "%23",
  12. ];
  13. // RegExp patterns to URL-decode special characters for local filesystem paths
  14. let urlDecodePatterns = [
  15. /\%23/g, "#",
  16. /\%24/g, "$",
  17. /\%26/g, "&",
  18. /\%2C/g, ",",
  19. /\%40/g, "@"
  20. ];
  21. exports.parse = require("url").parse;
  22. exports.resolve = require("url").resolve;
  23. /**
  24. * Returns the current working directory (in Node) or the current page URL (in browsers).
  25. *
  26. * @returns {string}
  27. */
  28. exports.cwd = function cwd () {
  29. if (process.browser) {
  30. return location.href;
  31. }
  32. let path = process.cwd();
  33. let lastChar = path.slice(-1);
  34. if (lastChar === "/" || lastChar === "\\") {
  35. return path;
  36. }
  37. else {
  38. return path + "/";
  39. }
  40. };
  41. /**
  42. * Returns the protocol of the given URL, or `undefined` if it has no protocol.
  43. *
  44. * @param {string} path
  45. * @returns {?string}
  46. */
  47. exports.getProtocol = function getProtocol (path) {
  48. let match = protocolPattern.exec(path);
  49. if (match) {
  50. return match[1].toLowerCase();
  51. }
  52. };
  53. /**
  54. * Returns the lowercased file extension of the given URL,
  55. * or an empty string if it has no extension.
  56. *
  57. * @param {string} path
  58. * @returns {string}
  59. */
  60. exports.getExtension = function getExtension (path) {
  61. let lastDot = path.lastIndexOf(".");
  62. if (lastDot >= 0) {
  63. return url.stripQuery(path.substr(lastDot).toLowerCase());
  64. }
  65. return "";
  66. };
  67. /**
  68. * Removes the query, if any, from the given path.
  69. *
  70. * @param {string} path
  71. * @returns {string}
  72. */
  73. exports.stripQuery = function stripQuery (path) {
  74. let queryIndex = path.indexOf("?");
  75. if (queryIndex >= 0) {
  76. path = path.substr(0, queryIndex);
  77. }
  78. return path;
  79. };
  80. /**
  81. * Returns the hash (URL fragment), of the given path.
  82. * If there is no hash, then the root hash ("#") is returned.
  83. *
  84. * @param {string} path
  85. * @returns {string}
  86. */
  87. exports.getHash = function getHash (path) {
  88. let hashIndex = path.indexOf("#");
  89. if (hashIndex >= 0) {
  90. return path.substr(hashIndex);
  91. }
  92. return "#";
  93. };
  94. /**
  95. * Removes the hash (URL fragment), if any, from the given path.
  96. *
  97. * @param {string} path
  98. * @returns {string}
  99. */
  100. exports.stripHash = function stripHash (path) {
  101. let hashIndex = path.indexOf("#");
  102. if (hashIndex >= 0) {
  103. path = path.substr(0, hashIndex);
  104. }
  105. return path;
  106. };
  107. /**
  108. * Determines whether the given path is an HTTP(S) URL.
  109. *
  110. * @param {string} path
  111. * @returns {boolean}
  112. */
  113. exports.isHttp = function isHttp (path) {
  114. let protocol = url.getProtocol(path);
  115. if (protocol === "http" || protocol === "https") {
  116. return true;
  117. }
  118. else if (protocol === undefined) {
  119. // There is no protocol. If we're running in a browser, then assume it's HTTP.
  120. return process.browser;
  121. }
  122. else {
  123. // It's some other protocol, such as "ftp://", "mongodb://", etc.
  124. return false;
  125. }
  126. };
  127. /**
  128. * Determines whether the given path is a filesystem path.
  129. * This includes "file://" URLs.
  130. *
  131. * @param {string} path
  132. * @returns {boolean}
  133. */
  134. exports.isFileSystemPath = function isFileSystemPath (path) {
  135. if (process.browser) {
  136. // We're running in a browser, so assume that all paths are URLs.
  137. // This way, even relative paths will be treated as URLs rather than as filesystem paths
  138. return false;
  139. }
  140. let protocol = url.getProtocol(path);
  141. return protocol === undefined || protocol === "file";
  142. };
  143. /**
  144. * Converts a filesystem path to a properly-encoded URL.
  145. *
  146. * This is intended to handle situations where JSON Schema $Ref Parser is called
  147. * with a filesystem path that contains characters which are not allowed in URLs.
  148. *
  149. * @example
  150. * The following filesystem paths would be converted to the following URLs:
  151. *
  152. * <"!@#$%^&*+=?'>.json ==> %3C%22!@%23$%25%5E&*+=%3F\'%3E.json
  153. * C:\\My Documents\\File (1).json ==> C:/My%20Documents/File%20(1).json
  154. * file://Project #42/file.json ==> file://Project%20%2342/file.json
  155. *
  156. * @param {string} path
  157. * @returns {string}
  158. */
  159. exports.fromFileSystemPath = function fromFileSystemPath (path) {
  160. // Step 1: On Windows, replace backslashes with forward slashes,
  161. // rather than encoding them as "%5C"
  162. if (isWindows) {
  163. path = path.replace(/\\/g, "/");
  164. }
  165. // Step 2: `encodeURI` will take care of MOST characters
  166. path = encodeURI(path);
  167. // Step 3: Manually encode characters that are not encoded by `encodeURI`.
  168. // This includes characters such as "#" and "?", which have special meaning in URLs,
  169. // but are just normal characters in a filesystem path.
  170. for (let i = 0; i < urlEncodePatterns.length; i += 2) {
  171. path = path.replace(urlEncodePatterns[i], urlEncodePatterns[i + 1]);
  172. }
  173. return path;
  174. };
  175. /**
  176. * Converts a URL to a local filesystem path.
  177. *
  178. * @param {string} path
  179. * @param {boolean} [keepFileProtocol] - If true, then "file://" will NOT be stripped
  180. * @returns {string}
  181. */
  182. exports.toFileSystemPath = function toFileSystemPath (path, keepFileProtocol) {
  183. // Step 1: `decodeURI` will decode characters such as Cyrillic characters, spaces, etc.
  184. path = decodeURI(path);
  185. // Step 2: Manually decode characters that are not decoded by `decodeURI`.
  186. // This includes characters such as "#" and "?", which have special meaning in URLs,
  187. // but are just normal characters in a filesystem path.
  188. for (let i = 0; i < urlDecodePatterns.length; i += 2) {
  189. path = path.replace(urlDecodePatterns[i], urlDecodePatterns[i + 1]);
  190. }
  191. // Step 3: If it's a "file://" URL, then format it consistently
  192. // or convert it to a local filesystem path
  193. let isFileUrl = path.substr(0, 7).toLowerCase() === "file://";
  194. if (isFileUrl) {
  195. // Strip-off the protocol, and the initial "/", if there is one
  196. path = path[7] === "/" ? path.substr(8) : path.substr(7);
  197. // insert a colon (":") after the drive letter on Windows
  198. if (isWindows && path[1] === "/") {
  199. path = path[0] + ":" + path.substr(1);
  200. }
  201. if (keepFileProtocol) {
  202. // Return the consistently-formatted "file://" URL
  203. path = "file:///" + path;
  204. }
  205. else {
  206. // Convert the "file://" URL to a local filesystem path.
  207. // On Windows, it will start with something like "C:/".
  208. // On Posix, it will start with "/"
  209. isFileUrl = false;
  210. path = isWindows ? path : "/" + path;
  211. }
  212. }
  213. // Step 4: Normalize Windows paths (unless it's a "file://" URL)
  214. if (isWindows && !isFileUrl) {
  215. // Replace forward slashes with backslashes
  216. path = path.replace(forwardSlashPattern, "\\");
  217. // Capitalize the drive letter
  218. if (path.substr(1, 2) === ":\\") {
  219. path = path[0].toUpperCase() + path.substr(1);
  220. }
  221. }
  222. return path;
  223. };
  224. /**
  225. * Converts a $ref pointer to a valid JSON Path.
  226. *
  227. * @param {string} pointer
  228. * @returns {Array<number | string>}
  229. */
  230. exports.safePointerToPath = function safePointerToPath (pointer) {
  231. if (pointer.length <= 1 || pointer[0] !== "#" || pointer[1] !== "/") {
  232. return [];
  233. }
  234. return pointer
  235. .slice(2)
  236. .split("/")
  237. .map((value) => {
  238. return decodeURIComponent(value)
  239. .replace(jsonPointerSlash, "/")
  240. .replace(jsonPointerTilde, "~");
  241. });
  242. };