Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions lib/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -707,8 +707,17 @@ const win32 = {
// We found a mismatch before the first common path separator was seen, so
// return the original `to`.
if (i !== length) {
if (lastCommonSep === -1)
return toOrig;
if (lastCommonSep === -1) {
// Two driveless rooted paths (e.g. `\\foo` and `\\bar`) share the
// current-drive root, so a relative path can still be produced even
// when their first segments differ. For any other roots (different
// drives, UNC vs. drive, etc.) no relative path exists.
if (fromStart === 1 && toStart === 1) {
lastCommonSep = 0;
} else {
return toOrig;
}
}
} else {
if (toLen > length) {
if (StringPrototypeCharCodeAt(to, toStart + i) ===
Expand All @@ -717,9 +726,12 @@ const win32 = {
// For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz'
return StringPrototypeSlice(toOrig, toStart + i + 1);
}
if (i === 2) {
if (i === 2 && fromStart === 0) {
// We get here if `from` is the device root.
// For example: from='C:\\'; to='C:\\foo'
// `fromStart === 0` ensures this only applies to device-rooted paths
// (e.g. `C:\\`) and not to driveless rooted paths (e.g. `\\foo`),
// where index 2 is regular path content rather than a device root.
return StringPrototypeSlice(toOrig, toStart + i);
}
}
Expand All @@ -729,9 +741,11 @@ const win32 = {
// We get here if `to` is the exact base path for `from`.
// For example: from='C:\\foo\\bar'; to='C:\\foo'
lastCommonSep = i;
} else if (i === 2) {
} else if (i === 2 && toStart === 0) {
// We get here if `to` is the device root.
// For example: from='C:\\foo\\bar'; to='C:\\'
// `toStart === 0` ensures this only applies to device-rooted paths
// (e.g. `C:\\`) and not to driveless rooted paths (e.g. `\\foo`).
lastCommonSep = 3;
}
}
Expand All @@ -753,8 +767,17 @@ const win32 = {

// Lastly, append the rest of the destination (`to`) path that comes after
// the common path parts
if (out.length > 0)
if (out.length > 0) {
// The remaining `to` tail normally begins with the common path separator,
// which joins it to the `..` segments. For driveless rooted paths (e.g.
// `\\foo`) the leading separator was trimmed and `lastCommonSep` is the
// root, so insert the missing separator to avoid gluing `..` to the tail.
if (toStart < toEnd &&
StringPrototypeCharCodeAt(toOrig, toStart) !== CHAR_BACKWARD_SLASH) {
return `${out}\\${StringPrototypeSlice(toOrig, toStart, toEnd)}`;
}
return `${out}${StringPrototypeSlice(toOrig, toStart, toEnd)}`;
}

if (StringPrototypeCharCodeAt(toOrig, toStart) === CHAR_BACKWARD_SLASH)
++toStart;
Expand Down
17 changes: 17 additions & 0 deletions test/parallel/test-path-relative.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,23 @@ const relativeTests = [
['c:\\İ\\a\\i̇', 'c:\\İ\\b\\İ\\test.txt', '..\\..\\b\\İ\\test.txt'],
['c:\\i̇\\a\\İ', 'c:\\İ\\b\\İ\\test.txt', '..\\..\\b\\İ\\test.txt'],
['c:\\ß\\a\\ß', 'c:\\ß\\b\\ß\\test.txt', '..\\..\\b\\ß\\test.txt'],
// Driveless rooted paths (absolute relative to the current drive). These
// are structurally equivalent to POSIX paths and must not glue the `..`
// segments to the destination tail or trip the device-root shortcuts.
// Refs: https://github.com/nodejs/node/issues/63600
['\\aaaa\\bbbb', '\\aaaa', '..'],
['\\aaaa\\bbbb', '\\cccc', '..\\..\\cccc'],
['\\aaaa\\bbbb', '\\aaaa\\bbbb', ''],
['\\aaaa\\bbbb', '\\aaaa\\cccc', '..\\cccc'],
['\\aaaa\\', '\\aaaa\\cccc', 'cccc'],
['\\', '\\aaaa\\bbbb', 'aaaa\\bbbb'],
['\\aa\\bb', '\\a', '..\\..\\a'],
['\\aa', '\\a', '..\\a'],
['\\abc\\d', '\\ab', '..\\..\\ab'],
['\\ab', '\\abcd', '..\\abcd'],
['\\foo\\bar', '\\f', '..\\..\\f'],
['\\a', '\\a\\b\\c', 'b\\c'],
['\\page1\\page2\\foo', '\\', '..\\..\\..'],
],
],
[ path.posix.relative,
Expand Down
Loading