Skip to content

Commit eefd322

Browse files
TimothyGuMylesBorins
authored andcommitted
path: fix win32 volume-relative paths
`path.resolve()` and `path.join()` are left alone in this commit due to the lack of clear semantics. Backport-PR-URL: #14787 PR-URL: #14440 Fixes: #14405 Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: Tobias Nießen <[email protected]>
1 parent c2a0a93 commit eefd322

File tree

3 files changed

+97
-58
lines changed

3 files changed

+97
-58
lines changed

lib/path.js

+25-20
Original file line numberDiff line numberDiff line change
@@ -885,14 +885,28 @@ const win32 = {
885885

886886
extname: function extname(path) {
887887
assertPath(path);
888+
var start = 0;
888889
var startDot = -1;
889890
var startPart = 0;
890891
var end = -1;
891892
var matchedSlash = true;
892893
// Track the state of characters (if any) we see before our first dot and
893894
// after any path separator we find
894895
var preDotState = 0;
895-
for (var i = path.length - 1; i >= 0; --i) {
896+
897+
// Check for a drive letter prefix so as not to mistake the following
898+
// path separator as an extra separator at the end of the path that can be
899+
// disregarded
900+
if (path.length >= 2) {
901+
const code = path.charCodeAt(0);
902+
if (path.charCodeAt(1) === 58/*:*/ &&
903+
((code >= 65/*A*/ && code <= 90/*Z*/) ||
904+
(code >= 97/*a*/ && code <= 122/*z*/))) {
905+
start = startPart = 2;
906+
}
907+
}
908+
909+
for (var i = path.length - 1; i >= start; --i) {
896910
const code = path.charCodeAt(i);
897911
if (code === 47/*/*/ || code === 92/*\*/) {
898912
// If we reached a path separator that was not part of a set of path
@@ -956,15 +970,12 @@ const win32 = {
956970
var len = path.length;
957971
var rootEnd = 0;
958972
var code = path.charCodeAt(0);
959-
var isAbsolute = false;
960973

961974
// Try to match a root
962975
if (len > 1) {
963976
if (code === 47/*/*/ || code === 92/*\*/) {
964977
// Possible UNC root
965978

966-
isAbsolute = true;
967-
968979
code = path.charCodeAt(1);
969980
rootEnd = 1;
970981
if (code === 47/*/*/ || code === 92/*\*/) {
@@ -1023,7 +1034,6 @@ const win32 = {
10231034
ret.root = ret.dir = path.slice(0, 3);
10241035
return ret;
10251036
}
1026-
isAbsolute = true;
10271037
rootEnd = 3;
10281038
}
10291039
} else {
@@ -1045,7 +1055,7 @@ const win32 = {
10451055
ret.root = path.slice(0, rootEnd);
10461056

10471057
var startDot = -1;
1048-
var startPart = 0;
1058+
var startPart = rootEnd;
10491059
var end = -1;
10501060
var matchedSlash = true;
10511061
var i = path.length - 1;
@@ -1094,26 +1104,21 @@ const win32 = {
10941104
startDot === end - 1 &&
10951105
startDot === startPart + 1)) {
10961106
if (end !== -1) {
1097-
if (startPart === 0 && isAbsolute)
1098-
ret.base = ret.name = path.slice(rootEnd, end);
1099-
else
1100-
ret.base = ret.name = path.slice(startPart, end);
1107+
ret.base = ret.name = path.slice(startPart, end);
11011108
}
11021109
} else {
1103-
if (startPart === 0 && isAbsolute) {
1104-
ret.name = path.slice(rootEnd, startDot);
1105-
ret.base = path.slice(rootEnd, end);
1106-
} else {
1107-
ret.name = path.slice(startPart, startDot);
1108-
ret.base = path.slice(startPart, end);
1109-
}
1110+
ret.name = path.slice(startPart, startDot);
1111+
ret.base = path.slice(startPart, end);
11101112
ret.ext = path.slice(startDot, end);
11111113
}
11121114

1113-
if (startPart > 0)
1115+
// If the directory is the root, use the entire root as the `dir` including
1116+
// the trailing slash if any (`C:\abc` -> `C:\`). Otherwise, strip out the
1117+
// trailing slash (`C:\abc\def` -> `C:\abc`).
1118+
if (startPart > 0 && startPart !== rootEnd)
11141119
ret.dir = path.slice(0, startPart - 1);
1115-
else if (isAbsolute)
1116-
ret.dir = path.slice(0, rootEnd);
1120+
else
1121+
ret.dir = ret.root;
11171122

11181123
return ret;
11191124
},

test/parallel/test-path-parse-format.js

+45-37
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,33 @@ const assert = require('assert');
44
const path = require('path');
55

66
const winPaths = [
7-
'C:\\path\\dir\\index.html',
8-
'C:\\another_path\\DIR\\1\\2\\33\\\\index',
9-
'another_path\\DIR with spaces\\1\\2\\33\\index',
10-
'\\foo\\C:',
11-
'file',
12-
'.\\file',
13-
'C:\\',
14-
'C:',
15-
'\\',
16-
'',
7+
// [path, root]
8+
['C:\\path\\dir\\index.html', 'C:\\'],
9+
['C:\\another_path\\DIR\\1\\2\\33\\\\index', 'C:\\'],
10+
['another_path\\DIR with spaces\\1\\2\\33\\index', ''],
11+
['\\', '\\'],
12+
['\\foo\\C:', '\\'],
13+
['file', ''],
14+
['file:stream', ''],
15+
['.\\file', ''],
16+
['C:', 'C:'],
17+
['C:.', 'C:'],
18+
['C:..', 'C:'],
19+
['C:abc', 'C:'],
20+
['C:\\', 'C:\\'],
21+
['C:\\abc', 'C:\\' ],
22+
['', ''],
1723

1824
// unc
19-
'\\\\server\\share\\file_path',
20-
'\\\\server two\\shared folder\\file path.zip',
21-
'\\\\teela\\admin$\\system32',
22-
'\\\\?\\UNC\\server\\share'
25+
['\\\\server\\share\\file_path', '\\\\server\\share\\'],
26+
['\\\\server two\\shared folder\\file path.zip',
27+
'\\\\server two\\shared folder\\'],
28+
['\\\\teela\\admin$\\system32', '\\\\teela\\admin$\\'],
29+
['\\\\?\\UNC\\server\\share', '\\\\?\\UNC\\']
2330
];
2431

2532
const winSpecialCaseParseTests = [
2633
['/foo/bar', { root: '/' }],
27-
['C:', { root: 'C:', dir: 'C:', base: '' }],
28-
['C:\\', { root: 'C:\\', dir: 'C:\\', base: '' }]
2934
];
3035

3136
const winSpecialCaseFormatTests = [
@@ -39,26 +44,27 @@ const winSpecialCaseFormatTests = [
3944
];
4045

4146
const unixPaths = [
42-
'/home/user/dir/file.txt',
43-
'/home/user/a dir/another File.zip',
44-
'/home/user/a dir//another&File.',
45-
'/home/user/a$$$dir//another File.zip',
46-
'user/dir/another File.zip',
47-
'file',
48-
'.\\file',
49-
'./file',
50-
'C:\\foo',
51-
'/',
52-
'',
53-
'.',
54-
'..',
55-
'/foo',
56-
'/foo.',
57-
'/foo.bar',
58-
'/.',
59-
'/.foo',
60-
'/.foo.bar',
61-
'/foo/bar.baz',
47+
// [path, root]
48+
['/home/user/dir/file.txt', '/'],
49+
['/home/user/a dir/another File.zip', '/'],
50+
['/home/user/a dir//another&File.', '/'],
51+
['/home/user/a$$$dir//another File.zip', '/'],
52+
['user/dir/another File.zip', ''],
53+
['file', ''],
54+
['.\\file', ''],
55+
['./file', ''],
56+
['C:\\foo', ''],
57+
['/', '/'],
58+
['', ''],
59+
['.', ''],
60+
['..', ''],
61+
['/foo', '/'],
62+
['/foo.', '/'],
63+
['/foo.bar', '/'],
64+
['/.', '/'],
65+
['/.foo', '/'],
66+
['/.foo.bar', '/'],
67+
['/foo/bar.baz', '/']
6268
];
6369

6470
const unixSpecialCaseFormatTests = [
@@ -169,14 +175,16 @@ function checkErrors(path) {
169175
}
170176

171177
function checkParseFormat(path, paths) {
172-
paths.forEach(function(element) {
178+
paths.forEach(function([element, root]) {
173179
const output = path.parse(element);
174180
assert.strictEqual(typeof output.root, 'string');
175181
assert.strictEqual(typeof output.dir, 'string');
176182
assert.strictEqual(typeof output.base, 'string');
177183
assert.strictEqual(typeof output.ext, 'string');
178184
assert.strictEqual(typeof output.name, 'string');
179185
assert.strictEqual(path.format(output), element);
186+
assert.strictEqual(output.root, root);
187+
assert(output.dir.startsWith(output.root));
180188
assert.strictEqual(output.dir, output.dir ? path.dirname(element) : '');
181189
assert.strictEqual(output.base, path.basename(element));
182190
assert.strictEqual(output.ext, path.extname(element));

test/parallel/test-path.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ assert.strictEqual(path.win32.basename('aaa\\bbb', 'bbb'), 'bbb');
5151
assert.strictEqual(path.win32.basename('aaa\\bbb\\\\\\\\', 'bbb'), 'bbb');
5252
assert.strictEqual(path.win32.basename('aaa\\bbb', 'bb'), 'b');
5353
assert.strictEqual(path.win32.basename('aaa\\bbb', 'b'), 'bb');
54+
assert.strictEqual(path.win32.basename('C:'), '');
55+
assert.strictEqual(path.win32.basename('C:.'), '.');
56+
assert.strictEqual(path.win32.basename('C:\\'), '');
57+
assert.strictEqual(path.win32.basename('C:\\dir\\base.ext'), 'base.ext');
58+
assert.strictEqual(path.win32.basename('C:\\basename.ext'), 'basename.ext');
59+
assert.strictEqual(path.win32.basename('C:basename.ext'), 'basename.ext');
60+
assert.strictEqual(path.win32.basename('C:basename.ext\\'), 'basename.ext');
61+
assert.strictEqual(path.win32.basename('C:basename.ext\\\\'), 'basename.ext');
62+
assert.strictEqual(path.win32.basename('C:foo'), 'foo');
63+
assert.strictEqual(path.win32.basename('file:stream'), 'file:stream');
5464

5565
// On unix a backslash is just treated as any other character.
5666
assert.strictEqual(path.posix.basename('\\dir\\basename.ext'),
@@ -99,6 +109,8 @@ assert.strictEqual(path.win32.dirname('c:foo\\'), 'c:');
99109
assert.strictEqual(path.win32.dirname('c:foo\\bar'), 'c:foo');
100110
assert.strictEqual(path.win32.dirname('c:foo\\bar\\'), 'c:foo');
101111
assert.strictEqual(path.win32.dirname('c:foo\\bar\\baz'), 'c:foo\\bar');
112+
assert.strictEqual(path.win32.dirname('file:stream'), '.');
113+
assert.strictEqual(path.win32.dirname('dir\\file:stream'), 'dir');
102114
assert.strictEqual(path.win32.dirname('\\\\unc\\share'),
103115
'\\\\unc\\share');
104116
assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo'),
@@ -166,6 +178,7 @@ assert.strictEqual(path.win32.dirname('foo'), '.');
166178
['file./', '.'],
167179
['file.//', '.'],
168180
].forEach((test) => {
181+
const expected = test[1];
169182
[path.posix.extname, path.win32.extname].forEach((extname) => {
170183
let input = test[0];
171184
let os;
@@ -176,12 +189,19 @@ assert.strictEqual(path.win32.dirname('foo'), '.');
176189
os = 'posix';
177190
}
178191
const actual = extname(input);
179-
const expected = test[1];
180192
const message = `path.${os}.extname(${JSON.stringify(input)})\n expect=${
181193
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
182194
if (actual !== expected)
183195
failures.push(`\n${message}`);
184196
});
197+
{
198+
const input = `C:${test[0].replace(slashRE, '\\')}`;
199+
const actual = path.win32.extname(input);
200+
const message = `path.win32.extname(${JSON.stringify(input)})\n expect=${
201+
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
202+
if (actual !== expected)
203+
failures.push(`\n${message}`);
204+
}
185205
});
186206
assert.strictEqual(failures.length, 0, failures.join(''));
187207

@@ -385,6 +405,12 @@ assert.strictEqual(path.win32.normalize('a//b//.'), 'a\\b');
385405
assert.strictEqual(path.win32.normalize('//server/share/dir/file.ext'),
386406
'\\\\server\\share\\dir\\file.ext');
387407
assert.strictEqual(path.win32.normalize('/a/b/c/../../../x/y/z'), '\\x\\y\\z');
408+
assert.strictEqual(path.win32.normalize('C:'), 'C:.');
409+
assert.strictEqual(path.win32.normalize('C:..\\abc'), 'C:..\\abc');
410+
assert.strictEqual(path.win32.normalize('C:..\\..\\abc\\..\\def'),
411+
'C:..\\..\\def');
412+
assert.strictEqual(path.win32.normalize('C:\\.'), 'C:\\');
413+
assert.strictEqual(path.win32.normalize('file:stream'), 'file:stream');
388414

389415
assert.strictEqual(path.posix.normalize('./fixtures///b/../b/c.js'),
390416
'fixtures/b/c.js');

0 commit comments

Comments
 (0)