| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- */
- "use strict";
- // A browser shim for the Node `path` builtin, exposing only the subset used by
- // `lib/util/path.js`: `posix.normalize`, `posix.dirname`, `win32.normalize`,
- // `win32.dirname` and a (posix) `basename`. It is a faithful port of Node's
- // `path` implementation and is wired in through the package `browser` field, so
- // Node, Deno and Bun keep using their native `path` and only browser bundles
- // use this. Inputs are always strings here, so Node's `validateString` guards
- // are omitted.
- const CHAR_UPPERCASE_A = 65;
- const CHAR_UPPERCASE_Z = 90;
- const CHAR_LOWERCASE_A = 97;
- const CHAR_LOWERCASE_Z = 122;
- const CHAR_DOT = 46;
- const CHAR_FORWARD_SLASH = 47;
- const CHAR_BACKWARD_SLASH = 92;
- const CHAR_COLON = 58;
- /**
- * @param {number} code char code
- * @returns {boolean} true for `/`
- */
- const isPosixPathSeparator = (code) => code === CHAR_FORWARD_SLASH;
- /**
- * @param {number} code char code
- * @returns {boolean} true for `/` or `\`
- */
- const isPathSeparator = (code) =>
- code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
- /**
- * @param {number} code char code
- * @returns {boolean} true for an ASCII letter (a windows device root)
- */
- const isWindowsDeviceRoot = (code) =>
- (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
- (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z);
- /**
- * Resolve `.` and `..` segments in a path. Ported from Node's internal
- * `normalizeString`.
- * @param {string} path path
- * @param {boolean} allowAboveRoot whether leading `..` may be kept
- * @param {string} separator path separator to emit
- * @param {(code: number) => boolean} isSeparator separator predicate
- * @returns {string} normalized path string (without root)
- */
- function normalizeString(path, allowAboveRoot, separator, isSeparator) {
- let res = "";
- let lastSegmentLength = 0;
- let lastSlash = -1;
- let dots = 0;
- let code = 0;
- for (let i = 0; i <= path.length; ++i) {
- if (i < path.length) {
- code = path.charCodeAt(i);
- } else if (isSeparator(code)) {
- break;
- } else {
- code = CHAR_FORWARD_SLASH;
- }
- if (isSeparator(code)) {
- if (lastSlash === i - 1 || dots === 1) {
- // NOOP
- } else if (dots === 2) {
- if (
- res.length < 2 ||
- lastSegmentLength !== 2 ||
- res.charCodeAt(res.length - 1) !== CHAR_DOT ||
- res.charCodeAt(res.length - 2) !== CHAR_DOT
- ) {
- if (res.length > 2) {
- const lastSlashIndex = res.lastIndexOf(separator);
- if (lastSlashIndex === -1) {
- res = "";
- lastSegmentLength = 0;
- } else {
- res = res.slice(0, lastSlashIndex);
- lastSegmentLength = res.length - 1 - res.lastIndexOf(separator);
- }
- lastSlash = i;
- dots = 0;
- continue;
- } else if (res.length !== 0) {
- res = "";
- lastSegmentLength = 0;
- lastSlash = i;
- dots = 0;
- continue;
- }
- }
- if (allowAboveRoot) {
- res += res.length > 0 ? `${separator}..` : "..";
- lastSegmentLength = 2;
- }
- } else {
- if (res.length > 0) {
- res += `${separator}${path.slice(lastSlash + 1, i)}`;
- } else {
- res = path.slice(lastSlash + 1, i);
- }
- lastSegmentLength = i - lastSlash - 1;
- }
- lastSlash = i;
- dots = 0;
- } else if (code === CHAR_DOT && dots !== -1) {
- ++dots;
- } else {
- dots = -1;
- }
- }
- return res;
- }
- /**
- * @param {string} path path
- * @returns {string} normalized posix path
- */
- function posixNormalize(path) {
- if (path.length === 0) return ".";
- const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH;
- const trailingSeparator =
- path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH;
- path = normalizeString(path, !isAbsolute, "/", isPosixPathSeparator);
- if (path.length === 0) {
- if (isAbsolute) return "/";
- return trailingSeparator ? "./" : ".";
- }
- if (trailingSeparator) path += "/";
- return isAbsolute ? `/${path}` : path;
- }
- /**
- * @param {string} path path
- * @returns {string} posix dirname
- */
- function posixDirname(path) {
- if (path.length === 0) return ".";
- const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH;
- let end = -1;
- let matchedSlash = true;
- for (let i = path.length - 1; i >= 1; --i) {
- if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
- if (!matchedSlash) {
- end = i;
- break;
- }
- } else {
- matchedSlash = false;
- }
- }
- if (end === -1) return hasRoot ? "/" : ".";
- if (hasRoot && end === 1) return "//";
- return path.slice(0, end);
- }
- /**
- * Normalizes drive paths (`C:\…`), UNC paths (`\\server\share\…`), DOS device
- * paths (`\\.\…`, `\\?\…`) and relative/normal segments like Node's
- * `path.win32.normalize`, including the CVE-2024-36139 colon-segment guard.
- *
- * Scope note: reserved Windows device names (`CON`, `COM1`, `LPT1`, …) are not
- * special-cased, so e.g. `\\.\COM1:` differs from Node. Such names cannot occur
- * in a browser (the only place this shim is used) and the resolver never routes
- * them to `win32.normalize`, so this does not affect resolution.
- * @param {string} path path
- * @returns {string} normalized win32 path
- */
- function win32Normalize(path) {
- const len = path.length;
- if (len === 0) return ".";
- let rootEnd = 0;
- /** @type {string | undefined} */
- let device;
- let isAbsolute = false;
- const code = path.charCodeAt(0);
- if (len === 1) {
- return isPosixPathSeparator(code) ? "\\" : path;
- }
- if (isPathSeparator(code)) {
- // Possible UNC root; an initial separator means an absolute path.
- isAbsolute = true;
- if (isPathSeparator(path.charCodeAt(1))) {
- // Matched double path separator at beginning
- let j = 2;
- let last = j;
- while (j < len && !isPathSeparator(path.charCodeAt(j))) j++;
- if (j < len && j !== last) {
- const firstPart = path.slice(last, j);
- last = j;
- while (j < len && isPathSeparator(path.charCodeAt(j))) j++;
- if (j < len && j !== last) {
- last = j;
- while (j < len && !isPathSeparator(path.charCodeAt(j))) j++;
- if (j === len || j !== last) {
- if (firstPart === "." || firstPart === "?") {
- // Device root, e.g. `\\.\pipe\…` or `\\?\C:\…`
- device = `\\\\${firstPart}`;
- rootEnd = 4;
- } else if (j === len) {
- // Matched a UNC root only
- return `\\\\${firstPart}\\${path.slice(last)}\\`;
- } else {
- // Matched a UNC root with leftovers
- device = `\\\\${firstPart}\\${path.slice(last, j)}`;
- rootEnd = j;
- }
- }
- }
- }
- } else {
- rootEnd = 1;
- }
- } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
- // Possible device root
- device = path.slice(0, 2);
- rootEnd = 2;
- if (len > 2 && isPathSeparator(path.charCodeAt(2))) {
- isAbsolute = true;
- rootEnd = 3;
- }
- }
- let tail =
- rootEnd < len
- ? normalizeString(path.slice(rootEnd), !isAbsolute, "\\", isPathSeparator)
- : "";
- if (tail.length === 0 && !isAbsolute) tail = ".";
- if (tail.length > 0 && isPathSeparator(path.charCodeAt(len - 1))) {
- tail += "\\";
- }
- if (!isAbsolute && device === undefined && path.includes(":")) {
- // A relative path that wasn't resolved to a device must not turn into
- // something Windows could read as an absolute/drive path (CVE-2024-36139).
- if (
- tail.length >= 2 &&
- isWindowsDeviceRoot(tail.charCodeAt(0)) &&
- tail.charCodeAt(1) === CHAR_COLON
- ) {
- return `.\\${tail}`;
- }
- let index = path.indexOf(":");
- do {
- if (index === len - 1 || isPathSeparator(path.charCodeAt(index + 1))) {
- return `.\\${tail}`;
- }
- } while ((index = path.indexOf(":", index + 1)) !== -1);
- }
- if (device === undefined) {
- return isAbsolute ? `\\${tail}` : tail;
- }
- return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`;
- }
- /**
- * @param {string} path path
- * @returns {string} win32 dirname
- */
- function win32Dirname(path) {
- const len = path.length;
- if (len === 0) return ".";
- let rootEnd = -1;
- let offset = 0;
- const code = path.charCodeAt(0);
- if (len === 1) {
- return isPathSeparator(code) ? path : ".";
- }
- if (isPathSeparator(code)) {
- // Possible UNC root
- rootEnd = offset = 1;
- if (isPathSeparator(path.charCodeAt(1))) {
- let j = 2;
- let last = j;
- while (j < len && !isPathSeparator(path.charCodeAt(j))) j++;
- if (j < len && j !== last) {
- last = j;
- while (j < len && isPathSeparator(path.charCodeAt(j))) j++;
- if (j < len && j !== last) {
- last = j;
- while (j < len && !isPathSeparator(path.charCodeAt(j))) j++;
- if (j === len) {
- // Matched a UNC root only
- return path;
- }
- if (j !== last) {
- // Matched a UNC root with leftovers
- rootEnd = offset = j + 1;
- }
- }
- }
- }
- } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
- rootEnd = len > 2 && isPathSeparator(path.charCodeAt(2)) ? 3 : 2;
- offset = rootEnd;
- }
- let end = -1;
- let matchedSlash = true;
- for (let i = len - 1; i >= offset; --i) {
- if (isPathSeparator(path.charCodeAt(i))) {
- if (!matchedSlash) {
- end = i;
- break;
- }
- } else {
- matchedSlash = false;
- }
- }
- if (end === -1) {
- if (rootEnd === -1) return ".";
- end = rootEnd;
- }
- return path.slice(0, end);
- }
- /**
- * Posix `basename` — the browser is treated as a posix platform, matching how
- * Node picks the posix variant for `path.basename` on non-Windows systems.
- * @param {string} path path
- * @param {string=} suffix optional suffix to strip
- * @returns {string} basename
- */
- function basename(path, suffix) {
- let start = 0;
- let end = -1;
- let matchedSlash = true;
- if (
- suffix !== undefined &&
- suffix.length > 0 &&
- suffix.length <= path.length
- ) {
- if (suffix === path) return "";
- let extIdx = suffix.length - 1;
- let firstNonSlashEnd = -1;
- for (let i = path.length - 1; i >= 0; --i) {
- const code = path.charCodeAt(i);
- if (code === CHAR_FORWARD_SLASH) {
- if (!matchedSlash) {
- start = i + 1;
- break;
- }
- } else {
- if (firstNonSlashEnd === -1) {
- matchedSlash = false;
- firstNonSlashEnd = i + 1;
- }
- if (extIdx >= 0) {
- if (code === suffix.charCodeAt(extIdx)) {
- if (--extIdx === -1) {
- end = i;
- }
- } else {
- extIdx = -1;
- end = firstNonSlashEnd;
- }
- }
- }
- }
- if (start === end) {
- end = firstNonSlashEnd;
- } else if (end === -1) {
- end = path.length;
- }
- return path.slice(start, end);
- }
- for (let i = path.length - 1; i >= 0; --i) {
- if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
- if (!matchedSlash) {
- start = i + 1;
- break;
- }
- } else if (end === -1) {
- matchedSlash = false;
- end = i + 1;
- }
- }
- if (end === -1) return "";
- return path.slice(start, end);
- }
- module.exports = {
- basename,
- posix: { normalize: posixNormalize, dirname: posixDirname },
- win32: { normalize: win32Normalize, dirname: win32Dirname },
- };
|