• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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