"use strict"; let isWindows = /^win/.test(process.platform), forwardSlashPattern = /\//g, protocolPattern = /^(\w{2,}):\/\//i, url = module.exports, jsonPointerSlash = /~1/g, jsonPointerTilde = /~0/g; // RegExp patterns to URL-encode special characters in local filesystem paths let urlEncodePatterns = [ /\?/g, "%3F", /\#/g, "%23", ]; // RegExp patterns to URL-decode special characters for local filesystem paths let urlDecodePatterns = [ /\%23/g, "#", /\%24/g, "$", /\%26/g, "&", /\%2C/g, ",", /\%40/g, "@" ]; exports.parse = require("url").parse; exports.resolve = require("url").resolve; /** * Returns the current working directory (in Node) or the current page URL (in browsers). * * @returns {string} */ exports.cwd = function cwd () { if (process.browser) { return location.href; } let path = process.cwd(); let lastChar = path.slice(-1); if (lastChar === "/" || lastChar === "\\") { return path; } else { return path + "/"; } }; /** * Returns the protocol of the given URL, or `undefined` if it has no protocol. * * @param {string} path * @returns {?string} */ exports.getProtocol = function getProtocol (path) { let match = protocolPattern.exec(path); if (match) { return match[1].toLowerCase(); } }; /** * Returns the lowercased file extension of the given URL, * or an empty string if it has no extension. * * @param {string} path * @returns {string} */ exports.getExtension = function getExtension (path) { let lastDot = path.lastIndexOf("."); if (lastDot >= 0) { return url.stripQuery(path.substr(lastDot).toLowerCase()); } return ""; }; /** * Removes the query, if any, from the given path. * * @param {string} path * @returns {string} */ exports.stripQuery = function stripQuery (path) { let queryIndex = path.indexOf("?"); if (queryIndex >= 0) { path = path.substr(0, queryIndex); } return path; }; /** * Returns the hash (URL fragment), of the given path. * If there is no hash, then the root hash ("#") is returned. * * @param {string} path * @returns {string} */ exports.getHash = function getHash (path) { let hashIndex = path.indexOf("#"); if (hashIndex >= 0) { return path.substr(hashIndex); } return "#"; }; /** * Removes the hash (URL fragment), if any, from the given path. * * @param {string} path * @returns {string} */ exports.stripHash = function stripHash (path) { let hashIndex = path.indexOf("#"); if (hashIndex >= 0) { path = path.substr(0, hashIndex); } return path; }; /** * Determines whether the given path is an HTTP(S) URL. * * @param {string} path * @returns {boolean} */ exports.isHttp = function isHttp (path) { let protocol = url.getProtocol(path); if (protocol === "http" || protocol === "https") { return true; } else if (protocol === undefined) { // There is no protocol. If we're running in a browser, then assume it's HTTP. return process.browser; } else { // It's some other protocol, such as "ftp://", "mongodb://", etc. return false; } }; /** * Determines whether the given path is a filesystem path. * This includes "file://" URLs. * * @param {string} path * @returns {boolean} */ exports.isFileSystemPath = function isFileSystemPath (path) { if (process.browser) { // We're running in a browser, so assume that all paths are URLs. // This way, even relative paths will be treated as URLs rather than as filesystem paths return false; } let protocol = url.getProtocol(path); return protocol === undefined || protocol === "file"; }; /** * Converts a filesystem path to a properly-encoded URL. * * This is intended to handle situations where JSON Schema $Ref Parser is called * with a filesystem path that contains characters which are not allowed in URLs. * * @example * The following filesystem paths would be converted to the following URLs: * * <"!@#$%^&*+=?'>.json ==> %3C%22!@%23$%25%5E&*+=%3F\'%3E.json * C:\\My Documents\\File (1).json ==> C:/My%20Documents/File%20(1).json * file://Project #42/file.json ==> file://Project%20%2342/file.json * * @param {string} path * @returns {string} */ exports.fromFileSystemPath = function fromFileSystemPath (path) { // Step 1: On Windows, replace backslashes with forward slashes, // rather than encoding them as "%5C" if (isWindows) { path = path.replace(/\\/g, "/"); } // Step 2: `encodeURI` will take care of MOST characters path = encodeURI(path); // Step 3: Manually encode characters that are not encoded by `encodeURI`. // This includes characters such as "#" and "?", which have special meaning in URLs, // but are just normal characters in a filesystem path. for (let i = 0; i < urlEncodePatterns.length; i += 2) { path = path.replace(urlEncodePatterns[i], urlEncodePatterns[i + 1]); } return path; }; /** * Converts a URL to a local filesystem path. * * @param {string} path * @param {boolean} [keepFileProtocol] - If true, then "file://" will NOT be stripped * @returns {string} */ exports.toFileSystemPath = function toFileSystemPath (path, keepFileProtocol) { // Step 1: `decodeURI` will decode characters such as Cyrillic characters, spaces, etc. path = decodeURI(path); // Step 2: Manually decode characters that are not decoded by `decodeURI`. // This includes characters such as "#" and "?", which have special meaning in URLs, // but are just normal characters in a filesystem path. for (let i = 0; i < urlDecodePatterns.length; i += 2) { path = path.replace(urlDecodePatterns[i], urlDecodePatterns[i + 1]); } // Step 3: If it's a "file://" URL, then format it consistently // or convert it to a local filesystem path let isFileUrl = path.substr(0, 7).toLowerCase() === "file://"; if (isFileUrl) { // Strip-off the protocol, and the initial "/", if there is one path = path[7] === "/" ? path.substr(8) : path.substr(7); // insert a colon (":") after the drive letter on Windows if (isWindows && path[1] === "/") { path = path[0] + ":" + path.substr(1); } if (keepFileProtocol) { // Return the consistently-formatted "file://" URL path = "file:///" + path; } else { // Convert the "file://" URL to a local filesystem path. // On Windows, it will start with something like "C:/". // On Posix, it will start with "/" isFileUrl = false; path = isWindows ? path : "/" + path; } } // Step 4: Normalize Windows paths (unless it's a "file://" URL) if (isWindows && !isFileUrl) { // Replace forward slashes with backslashes path = path.replace(forwardSlashPattern, "\\"); // Capitalize the drive letter if (path.substr(1, 2) === ":\\") { path = path[0].toUpperCase() + path.substr(1); } } return path; }; /** * Converts a $ref pointer to a valid JSON Path. * * @param {string} pointer * @returns {Array} */ exports.safePointerToPath = function safePointerToPath (pointer) { if (pointer.length <= 1 || pointer[0] !== "#" || pointer[1] !== "/") { return []; } return pointer .slice(2) .split("/") .map((value) => { return decodeURIComponent(value) .replace(jsonPointerSlash, "/") .replace(jsonPointerTilde, "~"); }); };