1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23 24const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes; 25const { 26 CHAR_UPPERCASE_A, 27 CHAR_LOWERCASE_A, 28 CHAR_UPPERCASE_Z, 29 CHAR_LOWERCASE_Z, 30 CHAR_DOT, 31 CHAR_FORWARD_SLASH, 32 CHAR_BACKWARD_SLASH, 33 CHAR_COLON, 34 CHAR_QUESTION_MARK, 35} = require('internal/constants'); 36const { validateString } = require('internal/validators'); 37 38function isPathSeparator(code) { 39 return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; 40} 41 42function isPosixPathSeparator(code) { 43 return code === CHAR_FORWARD_SLASH; 44} 45 46function isWindowsDeviceRoot(code) { 47 return (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) || 48 (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z); 49} 50 51// Resolves . and .. elements in a path with directory names 52function normalizeString(path, allowAboveRoot, separator, isPathSeparator) { 53 let res = ''; 54 let lastSegmentLength = 0; 55 let lastSlash = -1; 56 let dots = 0; 57 let code = 0; 58 for (let i = 0; i <= path.length; ++i) { 59 if (i < path.length) 60 code = path.charCodeAt(i); 61 else if (isPathSeparator(code)) 62 break; 63 else 64 code = CHAR_FORWARD_SLASH; 65 66 if (isPathSeparator(code)) { 67 if (lastSlash === i - 1 || dots === 1) { 68 // NOOP 69 } else if (dots === 2) { 70 if (res.length < 2 || lastSegmentLength !== 2 || 71 res.charCodeAt(res.length - 1) !== CHAR_DOT || 72 res.charCodeAt(res.length - 2) !== CHAR_DOT) { 73 if (res.length > 2) { 74 const lastSlashIndex = res.lastIndexOf(separator); 75 if (lastSlashIndex === -1) { 76 res = ''; 77 lastSegmentLength = 0; 78 } else { 79 res = res.slice(0, lastSlashIndex); 80 lastSegmentLength = res.length - 1 - res.lastIndexOf(separator); 81 } 82 lastSlash = i; 83 dots = 0; 84 continue; 85 } else if (res.length !== 0) { 86 res = ''; 87 lastSegmentLength = 0; 88 lastSlash = i; 89 dots = 0; 90 continue; 91 } 92 } 93 if (allowAboveRoot) { 94 res += res.length > 0 ? `${separator}..` : '..'; 95 lastSegmentLength = 2; 96 } 97 } else { 98 if (res.length > 0) 99 res += `${separator}${path.slice(lastSlash + 1, i)}`; 100 else 101 res = path.slice(lastSlash + 1, i); 102 lastSegmentLength = i - lastSlash - 1; 103 } 104 lastSlash = i; 105 dots = 0; 106 } else if (code === CHAR_DOT && dots !== -1) { 107 ++dots; 108 } else { 109 dots = -1; 110 } 111 } 112 return res; 113} 114 115function _format(sep, pathObject) { 116 if (pathObject === null || typeof pathObject !== 'object') { 117 throw new ERR_INVALID_ARG_TYPE('pathObject', 'Object', pathObject); 118 } 119 const dir = pathObject.dir || pathObject.root; 120 const base = pathObject.base || 121 `${pathObject.name || ''}${pathObject.ext || ''}`; 122 if (!dir) { 123 return base; 124 } 125 return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`; 126} 127 128const win32 = { 129 // path.resolve([from ...], to) 130 resolve(...args) { 131 let resolvedDevice = ''; 132 let resolvedTail = ''; 133 let resolvedAbsolute = false; 134 135 for (let i = args.length - 1; i >= -1; i--) { 136 let path; 137 if (i >= 0) { 138 path = args[i]; 139 validateString(path, 'path'); 140 141 // Skip empty entries 142 if (path.length === 0) { 143 continue; 144 } 145 } else if (resolvedDevice.length === 0) { 146 path = process.cwd(); 147 } else { 148 // Windows has the concept of drive-specific current working 149 // directories. If we've resolved a drive letter but not yet an 150 // absolute path, get cwd for that drive, or the process cwd if 151 // the drive cwd is not available. We're sure the device is not 152 // a UNC path at this points, because UNC paths are always absolute. 153 path = process.env[`=${resolvedDevice}`] || process.cwd(); 154 155 // Verify that a cwd was found and that it actually points 156 // to our drive. If not, default to the drive's root. 157 if (path === undefined || 158 (path.slice(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() && 159 path.charCodeAt(2) === CHAR_BACKWARD_SLASH)) { 160 path = `${resolvedDevice}\\`; 161 } 162 } 163 164 const len = path.length; 165 let rootEnd = 0; 166 let device = ''; 167 let isAbsolute = false; 168 const code = path.charCodeAt(0); 169 170 // Try to match a root 171 if (len === 1) { 172 if (isPathSeparator(code)) { 173 // `path` contains just a path separator 174 rootEnd = 1; 175 isAbsolute = true; 176 } 177 } else if (isPathSeparator(code)) { 178 // Possible UNC root 179 180 // If we started with a separator, we know we at least have an 181 // absolute path of some kind (UNC or otherwise) 182 isAbsolute = true; 183 184 if (isPathSeparator(path.charCodeAt(1))) { 185 // Matched double path separator at beginning 186 let j = 2; 187 let last = j; 188 // Match 1 or more non-path separators 189 while (j < len && !isPathSeparator(path.charCodeAt(j))) { 190 j++; 191 } 192 if (j < len && j !== last) { 193 const firstPart = path.slice(last, j); 194 // Matched! 195 last = j; 196 // Match 1 or more path separators 197 while (j < len && isPathSeparator(path.charCodeAt(j))) { 198 j++; 199 } 200 if (j < len && j !== last) { 201 // Matched! 202 last = j; 203 // Match 1 or more non-path separators 204 while (j < len && !isPathSeparator(path.charCodeAt(j))) { 205 j++; 206 } 207 if (j === len || j !== last) { 208 // We matched a UNC root 209 device = `\\\\${firstPart}\\${path.slice(last, j)}`; 210 rootEnd = j; 211 } 212 } 213 } 214 } else { 215 rootEnd = 1; 216 } 217 } else if (isWindowsDeviceRoot(code) && 218 path.charCodeAt(1) === CHAR_COLON) { 219 // Possible device root 220 device = path.slice(0, 2); 221 rootEnd = 2; 222 if (len > 2 && isPathSeparator(path.charCodeAt(2))) { 223 // Treat separator following drive name as an absolute path 224 // indicator 225 isAbsolute = true; 226 rootEnd = 3; 227 } 228 } 229 230 if (device.length > 0) { 231 if (resolvedDevice.length > 0) { 232 if (device.toLowerCase() !== resolvedDevice.toLowerCase()) 233 // This path points to another device so it is not applicable 234 continue; 235 } else { 236 resolvedDevice = device; 237 } 238 } 239 240 if (resolvedAbsolute) { 241 if (resolvedDevice.length > 0) 242 break; 243 } else { 244 resolvedTail = `${path.slice(rootEnd)}\\${resolvedTail}`; 245 resolvedAbsolute = isAbsolute; 246 if (isAbsolute && resolvedDevice.length > 0) { 247 break; 248 } 249 } 250 } 251 252 // At this point the path should be resolved to a full absolute path, 253 // but handle relative paths to be safe (might happen when process.cwd() 254 // fails) 255 256 // Normalize the tail path 257 resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', 258 isPathSeparator); 259 260 return resolvedAbsolute ? 261 `${resolvedDevice}\\${resolvedTail}` : 262 `${resolvedDevice}${resolvedTail}` || '.'; 263 }, 264 265 normalize(path) { 266 validateString(path, 'path'); 267 const len = path.length; 268 if (len === 0) 269 return '.'; 270 let rootEnd = 0; 271 let device; 272 let isAbsolute = false; 273 const code = path.charCodeAt(0); 274 275 // Try to match a root 276 if (len === 1) { 277 // `path` contains just a single char, exit early to avoid 278 // unnecessary work 279 return isPosixPathSeparator(code) ? '\\' : path; 280 } 281 if (isPathSeparator(code)) { 282 // Possible UNC root 283 284 // If we started with a separator, we know we at least have an absolute 285 // path of some kind (UNC or otherwise) 286 isAbsolute = true; 287 288 if (isPathSeparator(path.charCodeAt(1))) { 289 // Matched double path separator at beginning 290 let j = 2; 291 let last = j; 292 // Match 1 or more non-path separators 293 while (j < len && !isPathSeparator(path.charCodeAt(j))) { 294 j++; 295 } 296 if (j < len && j !== last) { 297 const firstPart = path.slice(last, j); 298 // Matched! 299 last = j; 300 // Match 1 or more path separators 301 while (j < len && isPathSeparator(path.charCodeAt(j))) { 302 j++; 303 } 304 if (j < len && j !== last) { 305 // Matched! 306 last = j; 307 // Match 1 or more non-path separators 308 while (j < len && !isPathSeparator(path.charCodeAt(j))) { 309 j++; 310 } 311 if (j === len) { 312 // We matched a UNC root only 313 // Return the normalized version of the UNC root since there 314 // is nothing left to process 315 return `\\\\${firstPart}\\${path.slice(last)}\\`; 316 } 317 if (j !== last) { 318 // We matched a UNC root with leftovers 319 device = `\\\\${firstPart}\\${path.slice(last, j)}`; 320 rootEnd = j; 321 } 322 } 323 } 324 } else { 325 rootEnd = 1; 326 } 327 } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { 328 // Possible device root 329 device = path.slice(0, 2); 330 rootEnd = 2; 331 if (len > 2 && isPathSeparator(path.charCodeAt(2))) { 332 // Treat separator following drive name as an absolute path 333 // indicator 334 isAbsolute = true; 335 rootEnd = 3; 336 } 337 } 338 339 let tail = rootEnd < len ? 340 normalizeString(path.slice(rootEnd), !isAbsolute, '\\', isPathSeparator) : 341 ''; 342 if (tail.length === 0 && !isAbsolute) 343 tail = '.'; 344 if (tail.length > 0 && isPathSeparator(path.charCodeAt(len - 1))) 345 tail += '\\'; 346 if (device === undefined) { 347 return isAbsolute ? `\\${tail}` : tail; 348 } 349 return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`; 350 }, 351 352 isAbsolute(path) { 353 validateString(path, 'path'); 354 const len = path.length; 355 if (len === 0) 356 return false; 357 358 const code = path.charCodeAt(0); 359 return isPathSeparator(code) || 360 // Possible device root 361 (len > 2 && 362 isWindowsDeviceRoot(code) && 363 path.charCodeAt(1) === CHAR_COLON && 364 isPathSeparator(path.charCodeAt(2))); 365 }, 366 367 join(...args) { 368 if (args.length === 0) 369 return '.'; 370 371 let joined; 372 let firstPart; 373 for (let i = 0; i < args.length; ++i) { 374 const arg = args[i]; 375 validateString(arg, 'path'); 376 if (arg.length > 0) { 377 if (joined === undefined) 378 joined = firstPart = arg; 379 else 380 joined += `\\${arg}`; 381 } 382 } 383 384 if (joined === undefined) 385 return '.'; 386 387 // Make sure that the joined path doesn't start with two slashes, because 388 // normalize() will mistake it for a UNC path then. 389 // 390 // This step is skipped when it is very clear that the user actually 391 // intended to point at a UNC path. This is assumed when the first 392 // non-empty string arguments starts with exactly two slashes followed by 393 // at least one more non-slash character. 394 // 395 // Note that for normalize() to treat a path as a UNC path it needs to 396 // have at least 2 components, so we don't filter for that here. 397 // This means that the user can use join to construct UNC paths from 398 // a server name and a share name; for example: 399 // path.join('//server', 'share') -> '\\\\server\\share\\') 400 let needsReplace = true; 401 let slashCount = 0; 402 if (isPathSeparator(firstPart.charCodeAt(0))) { 403 ++slashCount; 404 const firstLen = firstPart.length; 405 if (firstLen > 1 && isPathSeparator(firstPart.charCodeAt(1))) { 406 ++slashCount; 407 if (firstLen > 2) { 408 if (isPathSeparator(firstPart.charCodeAt(2))) 409 ++slashCount; 410 else { 411 // We matched a UNC path in the first part 412 needsReplace = false; 413 } 414 } 415 } 416 } 417 if (needsReplace) { 418 // Find any more consecutive slashes we need to replace 419 while (slashCount < joined.length && 420 isPathSeparator(joined.charCodeAt(slashCount))) { 421 slashCount++; 422 } 423 424 // Replace the slashes if needed 425 if (slashCount >= 2) 426 joined = `\\${joined.slice(slashCount)}`; 427 } 428 429 return win32.normalize(joined); 430 }, 431 432 // It will solve the relative path from `from` to `to`, for instance: 433 // from = 'C:\\orandea\\test\\aaa' 434 // to = 'C:\\orandea\\impl\\bbb' 435 // The output of the function should be: '..\\..\\impl\\bbb' 436 relative(from, to) { 437 validateString(from, 'from'); 438 validateString(to, 'to'); 439 440 if (from === to) 441 return ''; 442 443 const fromOrig = win32.resolve(from); 444 const toOrig = win32.resolve(to); 445 446 if (fromOrig === toOrig) 447 return ''; 448 449 from = fromOrig.toLowerCase(); 450 to = toOrig.toLowerCase(); 451 452 if (from === to) 453 return ''; 454 455 // Trim any leading backslashes 456 let fromStart = 0; 457 while (fromStart < from.length && 458 from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) { 459 fromStart++; 460 } 461 // Trim trailing backslashes (applicable to UNC paths only) 462 let fromEnd = from.length; 463 while (fromEnd - 1 > fromStart && 464 from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) { 465 fromEnd--; 466 } 467 const fromLen = fromEnd - fromStart; 468 469 // Trim any leading backslashes 470 let toStart = 0; 471 while (toStart < to.length && 472 to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) { 473 toStart++; 474 } 475 // Trim trailing backslashes (applicable to UNC paths only) 476 let toEnd = to.length; 477 while (toEnd - 1 > toStart && 478 to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) { 479 toEnd--; 480 } 481 const toLen = toEnd - toStart; 482 483 // Compare paths to find the longest common path from root 484 const length = fromLen < toLen ? fromLen : toLen; 485 let lastCommonSep = -1; 486 let i = 0; 487 for (; i < length; i++) { 488 const fromCode = from.charCodeAt(fromStart + i); 489 if (fromCode !== to.charCodeAt(toStart + i)) 490 break; 491 else if (fromCode === CHAR_BACKWARD_SLASH) 492 lastCommonSep = i; 493 } 494 495 // We found a mismatch before the first common path separator was seen, so 496 // return the original `to`. 497 if (i !== length) { 498 if (lastCommonSep === -1) 499 return toOrig; 500 } else { 501 if (toLen > length) { 502 if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) { 503 // We get here if `from` is the exact base path for `to`. 504 // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz' 505 return toOrig.slice(toStart + i + 1); 506 } 507 if (i === 2) { 508 // We get here if `from` is the device root. 509 // For example: from='C:\\'; to='C:\\foo' 510 return toOrig.slice(toStart + i); 511 } 512 } 513 if (fromLen > length) { 514 if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) { 515 // We get here if `to` is the exact base path for `from`. 516 // For example: from='C:\\foo\\bar'; to='C:\\foo' 517 lastCommonSep = i; 518 } else if (i === 2) { 519 // We get here if `to` is the device root. 520 // For example: from='C:\\foo\\bar'; to='C:\\' 521 lastCommonSep = 3; 522 } 523 } 524 if (lastCommonSep === -1) 525 lastCommonSep = 0; 526 } 527 528 let out = ''; 529 // Generate the relative path based on the path difference between `to` and 530 // `from` 531 for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { 532 if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) { 533 out += out.length === 0 ? '..' : '\\..'; 534 } 535 } 536 537 toStart += lastCommonSep; 538 539 // Lastly, append the rest of the destination (`to`) path that comes after 540 // the common path parts 541 if (out.length > 0) 542 return `${out}${toOrig.slice(toStart, toEnd)}`; 543 544 if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) 545 ++toStart; 546 return toOrig.slice(toStart, toEnd); 547 }, 548 549 toNamespacedPath(path) { 550 // Note: this will *probably* throw somewhere. 551 if (typeof path !== 'string') 552 return path; 553 554 if (path.length === 0) { 555 return ''; 556 } 557 558 const resolvedPath = win32.resolve(path); 559 560 if (resolvedPath.length <= 2) 561 return path; 562 563 if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) { 564 // Possible UNC root 565 if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) { 566 const code = resolvedPath.charCodeAt(2); 567 if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) { 568 // Matched non-long UNC root, convert the path to a long UNC path 569 return `\\\\?\\UNC\\${resolvedPath.slice(2)}`; 570 } 571 } 572 } else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) && 573 resolvedPath.charCodeAt(1) === CHAR_COLON && 574 resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH) { 575 // Matched device root, convert the path to a long UNC path 576 return `\\\\?\\${resolvedPath}`; 577 } 578 579 return path; 580 }, 581 582 dirname(path) { 583 validateString(path, 'path'); 584 const len = path.length; 585 if (len === 0) 586 return '.'; 587 let rootEnd = -1; 588 let offset = 0; 589 const code = path.charCodeAt(0); 590 591 if (len === 1) { 592 // `path` contains just a path separator, exit early to avoid 593 // unnecessary work or a dot. 594 return isPathSeparator(code) ? path : '.'; 595 } 596 597 // Try to match a root 598 if (isPathSeparator(code)) { 599 // Possible UNC root 600 601 rootEnd = offset = 1; 602 603 if (isPathSeparator(path.charCodeAt(1))) { 604 // Matched double path separator at beginning 605 let j = 2; 606 let last = j; 607 // Match 1 or more non-path separators 608 while (j < len && !isPathSeparator(path.charCodeAt(j))) { 609 j++; 610 } 611 if (j < len && j !== last) { 612 // Matched! 613 last = j; 614 // Match 1 or more path separators 615 while (j < len && isPathSeparator(path.charCodeAt(j))) { 616 j++; 617 } 618 if (j < len && j !== last) { 619 // Matched! 620 last = j; 621 // Match 1 or more non-path separators 622 while (j < len && !isPathSeparator(path.charCodeAt(j))) { 623 j++; 624 } 625 if (j === len) { 626 // We matched a UNC root only 627 return path; 628 } 629 if (j !== last) { 630 // We matched a UNC root with leftovers 631 632 // Offset by 1 to include the separator after the UNC root to 633 // treat it as a "normal root" on top of a (UNC) root 634 rootEnd = offset = j + 1; 635 } 636 } 637 } 638 } 639 // Possible device root 640 } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { 641 rootEnd = len > 2 && isPathSeparator(path.charCodeAt(2)) ? 3 : 2; 642 offset = rootEnd; 643 } 644 645 let end = -1; 646 let matchedSlash = true; 647 for (let i = len - 1; i >= offset; --i) { 648 if (isPathSeparator(path.charCodeAt(i))) { 649 if (!matchedSlash) { 650 end = i; 651 break; 652 } 653 } else { 654 // We saw the first non-path separator 655 matchedSlash = false; 656 } 657 } 658 659 if (end === -1) { 660 if (rootEnd === -1) 661 return '.'; 662 663 end = rootEnd; 664 } 665 return path.slice(0, end); 666 }, 667 668 basename(path, ext) { 669 if (ext !== undefined) 670 validateString(ext, 'ext'); 671 validateString(path, 'path'); 672 let start = 0; 673 let end = -1; 674 let matchedSlash = true; 675 676 // Check for a drive letter prefix so as not to mistake the following 677 // path separator as an extra separator at the end of the path that can be 678 // disregarded 679 if (path.length >= 2 && 680 isWindowsDeviceRoot(path.charCodeAt(0)) && 681 path.charCodeAt(1) === CHAR_COLON) { 682 start = 2; 683 } 684 685 if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { 686 if (ext === path) 687 return ''; 688 let extIdx = ext.length - 1; 689 let firstNonSlashEnd = -1; 690 for (let i = path.length - 1; i >= start; --i) { 691 const code = path.charCodeAt(i); 692 if (isPathSeparator(code)) { 693 // If we reached a path separator that was not part of a set of path 694 // separators at the end of the string, stop now 695 if (!matchedSlash) { 696 start = i + 1; 697 break; 698 } 699 } else { 700 if (firstNonSlashEnd === -1) { 701 // We saw the first non-path separator, remember this index in case 702 // we need it if the extension ends up not matching 703 matchedSlash = false; 704 firstNonSlashEnd = i + 1; 705 } 706 if (extIdx >= 0) { 707 // Try to match the explicit extension 708 if (code === ext.charCodeAt(extIdx)) { 709 if (--extIdx === -1) { 710 // We matched the extension, so mark this as the end of our path 711 // component 712 end = i; 713 } 714 } else { 715 // Extension does not match, so our result is the entire path 716 // component 717 extIdx = -1; 718 end = firstNonSlashEnd; 719 } 720 } 721 } 722 } 723 724 if (start === end) 725 end = firstNonSlashEnd; 726 else if (end === -1) 727 end = path.length; 728 return path.slice(start, end); 729 } 730 for (let i = path.length - 1; i >= start; --i) { 731 if (isPathSeparator(path.charCodeAt(i))) { 732 // If we reached a path separator that was not part of a set of path 733 // separators at the end of the string, stop now 734 if (!matchedSlash) { 735 start = i + 1; 736 break; 737 } 738 } else if (end === -1) { 739 // We saw the first non-path separator, mark this as the end of our 740 // path component 741 matchedSlash = false; 742 end = i + 1; 743 } 744 } 745 746 if (end === -1) 747 return ''; 748 return path.slice(start, end); 749 }, 750 751 extname(path) { 752 validateString(path, 'path'); 753 let start = 0; 754 let startDot = -1; 755 let startPart = 0; 756 let end = -1; 757 let matchedSlash = true; 758 // Track the state of characters (if any) we see before our first dot and 759 // after any path separator we find 760 let preDotState = 0; 761 762 // Check for a drive letter prefix so as not to mistake the following 763 // path separator as an extra separator at the end of the path that can be 764 // disregarded 765 766 if (path.length >= 2 && 767 path.charCodeAt(1) === CHAR_COLON && 768 isWindowsDeviceRoot(path.charCodeAt(0))) { 769 start = startPart = 2; 770 } 771 772 for (let i = path.length - 1; i >= start; --i) { 773 const code = path.charCodeAt(i); 774 if (isPathSeparator(code)) { 775 // If we reached a path separator that was not part of a set of path 776 // separators at the end of the string, stop now 777 if (!matchedSlash) { 778 startPart = i + 1; 779 break; 780 } 781 continue; 782 } 783 if (end === -1) { 784 // We saw the first non-path separator, mark this as the end of our 785 // extension 786 matchedSlash = false; 787 end = i + 1; 788 } 789 if (code === CHAR_DOT) { 790 // If this is our first dot, mark it as the start of our extension 791 if (startDot === -1) 792 startDot = i; 793 else if (preDotState !== 1) 794 preDotState = 1; 795 } else if (startDot !== -1) { 796 // We saw a non-dot and non-path separator before our dot, so we should 797 // have a good chance at having a non-empty extension 798 preDotState = -1; 799 } 800 } 801 802 if (startDot === -1 || 803 end === -1 || 804 // We saw a non-dot character immediately before the dot 805 preDotState === 0 || 806 // The (right-most) trimmed path component is exactly '..' 807 (preDotState === 1 && 808 startDot === end - 1 && 809 startDot === startPart + 1)) { 810 return ''; 811 } 812 return path.slice(startDot, end); 813 }, 814 815 format: _format.bind(null, '\\'), 816 817 parse(path) { 818 validateString(path, 'path'); 819 820 const ret = { root: '', dir: '', base: '', ext: '', name: '' }; 821 if (path.length === 0) 822 return ret; 823 824 const len = path.length; 825 let rootEnd = 0; 826 let code = path.charCodeAt(0); 827 828 if (len === 1) { 829 if (isPathSeparator(code)) { 830 // `path` contains just a path separator, exit early to avoid 831 // unnecessary work 832 ret.root = ret.dir = path; 833 return ret; 834 } 835 ret.base = ret.name = path; 836 return ret; 837 } 838 // Try to match a root 839 if (isPathSeparator(code)) { 840 // Possible UNC root 841 842 rootEnd = 1; 843 if (isPathSeparator(path.charCodeAt(1))) { 844 // Matched double path separator at beginning 845 let j = 2; 846 let last = j; 847 // Match 1 or more non-path separators 848 while (j < len && !isPathSeparator(path.charCodeAt(j))) { 849 j++; 850 } 851 if (j < len && j !== last) { 852 // Matched! 853 last = j; 854 // Match 1 or more path separators 855 while (j < len && isPathSeparator(path.charCodeAt(j))) { 856 j++; 857 } 858 if (j < len && j !== last) { 859 // Matched! 860 last = j; 861 // Match 1 or more non-path separators 862 while (j < len && !isPathSeparator(path.charCodeAt(j))) { 863 j++; 864 } 865 if (j === len) { 866 // We matched a UNC root only 867 rootEnd = j; 868 } else if (j !== last) { 869 // We matched a UNC root with leftovers 870 rootEnd = j + 1; 871 } 872 } 873 } 874 } 875 } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { 876 // Possible device root 877 if (len <= 2) { 878 // `path` contains just a drive root, exit early to avoid 879 // unnecessary work 880 ret.root = ret.dir = path; 881 return ret; 882 } 883 rootEnd = 2; 884 if (isPathSeparator(path.charCodeAt(2))) { 885 if (len === 3) { 886 // `path` contains just a drive root, exit early to avoid 887 // unnecessary work 888 ret.root = ret.dir = path; 889 return ret; 890 } 891 rootEnd = 3; 892 } 893 } 894 if (rootEnd > 0) 895 ret.root = path.slice(0, rootEnd); 896 897 let startDot = -1; 898 let startPart = rootEnd; 899 let end = -1; 900 let matchedSlash = true; 901 let i = path.length - 1; 902 903 // Track the state of characters (if any) we see before our first dot and 904 // after any path separator we find 905 let preDotState = 0; 906 907 // Get non-dir info 908 for (; i >= rootEnd; --i) { 909 code = path.charCodeAt(i); 910 if (isPathSeparator(code)) { 911 // If we reached a path separator that was not part of a set of path 912 // separators at the end of the string, stop now 913 if (!matchedSlash) { 914 startPart = i + 1; 915 break; 916 } 917 continue; 918 } 919 if (end === -1) { 920 // We saw the first non-path separator, mark this as the end of our 921 // extension 922 matchedSlash = false; 923 end = i + 1; 924 } 925 if (code === CHAR_DOT) { 926 // If this is our first dot, mark it as the start of our extension 927 if (startDot === -1) 928 startDot = i; 929 else if (preDotState !== 1) 930 preDotState = 1; 931 } else if (startDot !== -1) { 932 // We saw a non-dot and non-path separator before our dot, so we should 933 // have a good chance at having a non-empty extension 934 preDotState = -1; 935 } 936 } 937 938 if (end !== -1) { 939 if (startDot === -1 || 940 // We saw a non-dot character immediately before the dot 941 preDotState === 0 || 942 // The (right-most) trimmed path component is exactly '..' 943 (preDotState === 1 && 944 startDot === end - 1 && 945 startDot === startPart + 1)) { 946 ret.base = ret.name = path.slice(startPart, end); 947 } else { 948 ret.name = path.slice(startPart, startDot); 949 ret.base = path.slice(startPart, end); 950 ret.ext = path.slice(startDot, end); 951 } 952 } 953 954 // If the directory is the root, use the entire root as the `dir` including 955 // the trailing slash if any (`C:\abc` -> `C:\`). Otherwise, strip out the 956 // trailing slash (`C:\abc\def` -> `C:\abc`). 957 if (startPart > 0 && startPart !== rootEnd) 958 ret.dir = path.slice(0, startPart - 1); 959 else 960 ret.dir = ret.root; 961 962 return ret; 963 }, 964 965 sep: '\\', 966 delimiter: ';', 967 win32: null, 968 posix: null 969}; 970 971const posix = { 972 // path.resolve([from ...], to) 973 resolve(...args) { 974 let resolvedPath = ''; 975 let resolvedAbsolute = false; 976 977 for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) { 978 const path = i >= 0 ? args[i] : process.cwd(); 979 980 validateString(path, 'path'); 981 982 // Skip empty entries 983 if (path.length === 0) { 984 continue; 985 } 986 987 resolvedPath = `${path}/${resolvedPath}`; 988 resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; 989 } 990 991 // At this point the path should be resolved to a full absolute path, but 992 // handle relative paths to be safe (might happen when process.cwd() fails) 993 994 // Normalize the path 995 resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', 996 isPosixPathSeparator); 997 998 if (resolvedAbsolute) { 999 return `/${resolvedPath}`; 1000 } 1001 return resolvedPath.length > 0 ? resolvedPath : '.'; 1002 }, 1003 1004 normalize(path) { 1005 validateString(path, 'path'); 1006 1007 if (path.length === 0) 1008 return '.'; 1009 1010 const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; 1011 const trailingSeparator = 1012 path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH; 1013 1014 // Normalize the path 1015 path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator); 1016 1017 if (path.length === 0) { 1018 if (isAbsolute) 1019 return '/'; 1020 return trailingSeparator ? './' : '.'; 1021 } 1022 if (trailingSeparator) 1023 path += '/'; 1024 1025 return isAbsolute ? `/${path}` : path; 1026 }, 1027 1028 isAbsolute(path) { 1029 validateString(path, 'path'); 1030 return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH; 1031 }, 1032 1033 join(...args) { 1034 if (args.length === 0) 1035 return '.'; 1036 let joined; 1037 for (let i = 0; i < args.length; ++i) { 1038 const arg = args[i]; 1039 validateString(arg, 'path'); 1040 if (arg.length > 0) { 1041 if (joined === undefined) 1042 joined = arg; 1043 else 1044 joined += `/${arg}`; 1045 } 1046 } 1047 if (joined === undefined) 1048 return '.'; 1049 return posix.normalize(joined); 1050 }, 1051 1052 relative(from, to) { 1053 validateString(from, 'from'); 1054 validateString(to, 'to'); 1055 1056 if (from === to) 1057 return ''; 1058 1059 // Trim leading forward slashes. 1060 from = posix.resolve(from); 1061 to = posix.resolve(to); 1062 1063 if (from === to) 1064 return ''; 1065 1066 const fromStart = 1; 1067 const fromEnd = from.length; 1068 const fromLen = fromEnd - fromStart; 1069 const toStart = 1; 1070 const toLen = to.length - toStart; 1071 1072 // Compare paths to find the longest common path from root 1073 const length = (fromLen < toLen ? fromLen : toLen); 1074 let lastCommonSep = -1; 1075 let i = 0; 1076 for (; i < length; i++) { 1077 const fromCode = from.charCodeAt(fromStart + i); 1078 if (fromCode !== to.charCodeAt(toStart + i)) 1079 break; 1080 else if (fromCode === CHAR_FORWARD_SLASH) 1081 lastCommonSep = i; 1082 } 1083 if (i === length) { 1084 if (toLen > length) { 1085 if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) { 1086 // We get here if `from` is the exact base path for `to`. 1087 // For example: from='/foo/bar'; to='/foo/bar/baz' 1088 return to.slice(toStart + i + 1); 1089 } 1090 if (i === 0) { 1091 // We get here if `from` is the root 1092 // For example: from='/'; to='/foo' 1093 return to.slice(toStart + i); 1094 } 1095 } else if (fromLen > length) { 1096 if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) { 1097 // We get here if `to` is the exact base path for `from`. 1098 // For example: from='/foo/bar/baz'; to='/foo/bar' 1099 lastCommonSep = i; 1100 } else if (i === 0) { 1101 // We get here if `to` is the root. 1102 // For example: from='/foo/bar'; to='/' 1103 lastCommonSep = 0; 1104 } 1105 } 1106 } 1107 1108 let out = ''; 1109 // Generate the relative path based on the path difference between `to` 1110 // and `from`. 1111 for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { 1112 if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) { 1113 out += out.length === 0 ? '..' : '/..'; 1114 } 1115 } 1116 1117 // Lastly, append the rest of the destination (`to`) path that comes after 1118 // the common path parts. 1119 return `${out}${to.slice(toStart + lastCommonSep)}`; 1120 }, 1121 1122 toNamespacedPath(path) { 1123 // Non-op on posix systems 1124 return path; 1125 }, 1126 1127 dirname(path) { 1128 validateString(path, 'path'); 1129 if (path.length === 0) 1130 return '.'; 1131 const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH; 1132 let end = -1; 1133 let matchedSlash = true; 1134 for (let i = path.length - 1; i >= 1; --i) { 1135 if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { 1136 if (!matchedSlash) { 1137 end = i; 1138 break; 1139 } 1140 } else { 1141 // We saw the first non-path separator 1142 matchedSlash = false; 1143 } 1144 } 1145 1146 if (end === -1) 1147 return hasRoot ? '/' : '.'; 1148 if (hasRoot && end === 1) 1149 return '//'; 1150 return path.slice(0, end); 1151 }, 1152 1153 basename(path, ext) { 1154 if (ext !== undefined) 1155 validateString(ext, 'ext'); 1156 validateString(path, 'path'); 1157 1158 let start = 0; 1159 let end = -1; 1160 let matchedSlash = true; 1161 1162 if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { 1163 if (ext === path) 1164 return ''; 1165 let extIdx = ext.length - 1; 1166 let firstNonSlashEnd = -1; 1167 for (let i = path.length - 1; i >= 0; --i) { 1168 const code = path.charCodeAt(i); 1169 if (code === CHAR_FORWARD_SLASH) { 1170 // If we reached a path separator that was not part of a set of path 1171 // separators at the end of the string, stop now 1172 if (!matchedSlash) { 1173 start = i + 1; 1174 break; 1175 } 1176 } else { 1177 if (firstNonSlashEnd === -1) { 1178 // We saw the first non-path separator, remember this index in case 1179 // we need it if the extension ends up not matching 1180 matchedSlash = false; 1181 firstNonSlashEnd = i + 1; 1182 } 1183 if (extIdx >= 0) { 1184 // Try to match the explicit extension 1185 if (code === ext.charCodeAt(extIdx)) { 1186 if (--extIdx === -1) { 1187 // We matched the extension, so mark this as the end of our path 1188 // component 1189 end = i; 1190 } 1191 } else { 1192 // Extension does not match, so our result is the entire path 1193 // component 1194 extIdx = -1; 1195 end = firstNonSlashEnd; 1196 } 1197 } 1198 } 1199 } 1200 1201 if (start === end) 1202 end = firstNonSlashEnd; 1203 else if (end === -1) 1204 end = path.length; 1205 return path.slice(start, end); 1206 } 1207 for (let i = path.length - 1; i >= 0; --i) { 1208 if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { 1209 // If we reached a path separator that was not part of a set of path 1210 // separators at the end of the string, stop now 1211 if (!matchedSlash) { 1212 start = i + 1; 1213 break; 1214 } 1215 } else if (end === -1) { 1216 // We saw the first non-path separator, mark this as the end of our 1217 // path component 1218 matchedSlash = false; 1219 end = i + 1; 1220 } 1221 } 1222 1223 if (end === -1) 1224 return ''; 1225 return path.slice(start, end); 1226 }, 1227 1228 extname(path) { 1229 validateString(path, 'path'); 1230 let startDot = -1; 1231 let startPart = 0; 1232 let end = -1; 1233 let matchedSlash = true; 1234 // Track the state of characters (if any) we see before our first dot and 1235 // after any path separator we find 1236 let preDotState = 0; 1237 for (let i = path.length - 1; i >= 0; --i) { 1238 const code = path.charCodeAt(i); 1239 if (code === CHAR_FORWARD_SLASH) { 1240 // If we reached a path separator that was not part of a set of path 1241 // separators at the end of the string, stop now 1242 if (!matchedSlash) { 1243 startPart = i + 1; 1244 break; 1245 } 1246 continue; 1247 } 1248 if (end === -1) { 1249 // We saw the first non-path separator, mark this as the end of our 1250 // extension 1251 matchedSlash = false; 1252 end = i + 1; 1253 } 1254 if (code === CHAR_DOT) { 1255 // If this is our first dot, mark it as the start of our extension 1256 if (startDot === -1) 1257 startDot = i; 1258 else if (preDotState !== 1) 1259 preDotState = 1; 1260 } else if (startDot !== -1) { 1261 // We saw a non-dot and non-path separator before our dot, so we should 1262 // have a good chance at having a non-empty extension 1263 preDotState = -1; 1264 } 1265 } 1266 1267 if (startDot === -1 || 1268 end === -1 || 1269 // We saw a non-dot character immediately before the dot 1270 preDotState === 0 || 1271 // The (right-most) trimmed path component is exactly '..' 1272 (preDotState === 1 && 1273 startDot === end - 1 && 1274 startDot === startPart + 1)) { 1275 return ''; 1276 } 1277 return path.slice(startDot, end); 1278 }, 1279 1280 format: _format.bind(null, '/'), 1281 1282 parse(path) { 1283 validateString(path, 'path'); 1284 1285 const ret = { root: '', dir: '', base: '', ext: '', name: '' }; 1286 if (path.length === 0) 1287 return ret; 1288 const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; 1289 let start; 1290 if (isAbsolute) { 1291 ret.root = '/'; 1292 start = 1; 1293 } else { 1294 start = 0; 1295 } 1296 let startDot = -1; 1297 let startPart = 0; 1298 let end = -1; 1299 let matchedSlash = true; 1300 let i = path.length - 1; 1301 1302 // Track the state of characters (if any) we see before our first dot and 1303 // after any path separator we find 1304 let preDotState = 0; 1305 1306 // Get non-dir info 1307 for (; i >= start; --i) { 1308 const code = path.charCodeAt(i); 1309 if (code === CHAR_FORWARD_SLASH) { 1310 // If we reached a path separator that was not part of a set of path 1311 // separators at the end of the string, stop now 1312 if (!matchedSlash) { 1313 startPart = i + 1; 1314 break; 1315 } 1316 continue; 1317 } 1318 if (end === -1) { 1319 // We saw the first non-path separator, mark this as the end of our 1320 // extension 1321 matchedSlash = false; 1322 end = i + 1; 1323 } 1324 if (code === CHAR_DOT) { 1325 // If this is our first dot, mark it as the start of our extension 1326 if (startDot === -1) 1327 startDot = i; 1328 else if (preDotState !== 1) 1329 preDotState = 1; 1330 } else if (startDot !== -1) { 1331 // We saw a non-dot and non-path separator before our dot, so we should 1332 // have a good chance at having a non-empty extension 1333 preDotState = -1; 1334 } 1335 } 1336 1337 if (end !== -1) { 1338 const start = startPart === 0 && isAbsolute ? 1 : startPart; 1339 if (startDot === -1 || 1340 // We saw a non-dot character immediately before the dot 1341 preDotState === 0 || 1342 // The (right-most) trimmed path component is exactly '..' 1343 (preDotState === 1 && 1344 startDot === end - 1 && 1345 startDot === startPart + 1)) { 1346 ret.base = ret.name = path.slice(start, end); 1347 } else { 1348 ret.name = path.slice(start, startDot); 1349 ret.base = path.slice(start, end); 1350 ret.ext = path.slice(startDot, end); 1351 } 1352 } 1353 1354 if (startPart > 0) 1355 ret.dir = path.slice(0, startPart - 1); 1356 else if (isAbsolute) 1357 ret.dir = '/'; 1358 1359 return ret; 1360 }, 1361 1362 sep: '/', 1363 delimiter: ':', 1364 win32: null, 1365 posix: null 1366}; 1367 1368posix.win32 = win32.win32 = win32; 1369posix.posix = win32.posix = posix; 1370 1371// Legacy internal API, docs-only deprecated: DEP0080 1372win32._makeLong = win32.toNamespacedPath; 1373posix._makeLong = posix.toNamespacedPath; 1374 1375module.exports = process.platform === 'win32' ? win32 : posix; 1376