Skip to content

Commit 2791761

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

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
@@ -908,14 +908,28 @@ const win32 = {
908908

909909
extname: function extname(path) {
910910
assertPath(path);
911+
var start = 0;
911912
var startDot = -1;
912913
var startPart = 0;
913914
var end = -1;
914915
var matchedSlash = true;
915916
// Track the state of characters (if any) we see before our first dot and
916917
// after any path separator we find
917918
var preDotState = 0;
918-
for (var i = path.length - 1; i >= 0; --i) {
919+
920+
// Check for a drive letter prefix so as not to mistake the following
921+
// path separator as an extra separator at the end of the path that can be
922+
// disregarded
923+
if (path.length >= 2) {
924+
const code = path.charCodeAt(0);
925+
if (path.charCodeAt(1) === 58/*:*/ &&
926+
((code >= 65/*A*/ && code <= 90/*Z*/) ||
927+
(code >= 97/*a*/ && code <= 122/*z*/))) {
928+
start = startPart = 2;
929+
}
930+
}
931+
932+
for (var i = path.length - 1; i >= start; --i) {
919933
const code = path.charCodeAt(i);
920934
if (code === 47/*/*/ || code === 92/*\*/) {
921935
// If we reached a path separator that was not part of a set of path
@@ -978,15 +992,12 @@ const win32 = {
978992
var len = path.length;
979993
var rootEnd = 0;
980994
var code = path.charCodeAt(0);
981-
var isAbsolute = false;
982995

983996
// Try to match a root
984997
if (len > 1) {
985998
if (code === 47/*/*/ || code === 92/*\*/) {
986999
// Possible UNC root
9871000

988-
isAbsolute = true;
989-
9901001
code = path.charCodeAt(1);
9911002
rootEnd = 1;
9921003
if (code === 47/*/*/ || code === 92/*\*/) {
@@ -1045,7 +1056,6 @@ const win32 = {
10451056
ret.root = ret.dir = path;
10461057
return ret;
10471058
}
1048-
isAbsolute = true;
10491059
rootEnd = 3;
10501060
}
10511061
} else {
@@ -1067,7 +1077,7 @@ const win32 = {
10671077
ret.root = path.slice(0, rootEnd);
10681078

10691079
var startDot = -1;
1070-
var startPart = 0;
1080+
var startPart = rootEnd;
10711081
var end = -1;
10721082
var matchedSlash = true;
10731083
var i = path.length - 1;
@@ -1116,26 +1126,21 @@ const win32 = {
11161126
startDot === end - 1 &&
11171127
startDot === startPart + 1)) {
11181128
if (end !== -1) {
1119-
if (startPart === 0 && isAbsolute)
1120-
ret.base = ret.name = path.slice(rootEnd, end);
1121-
else
1122-
ret.base = ret.name = path.slice(startPart, end);
1129+
ret.base = ret.name = path.slice(startPart, end);
11231130
}
11241131
} else {
1125-
if (startPart === 0 && isAbsolute) {
1126-
ret.name = path.slice(rootEnd, startDot);
1127-
ret.base = path.slice(rootEnd, end);
1128-
} else {
1129-
ret.name = path.slice(startPart, startDot);
1130-
ret.base = path.slice(startPart, end);
1131-
}
1132+
ret.name = path.slice(startPart, startDot);
1133+
ret.base = path.slice(startPart, end);
11321134
ret.ext = path.slice(startDot, end);
11331135
}
11341136

1135-
if (startPart > 0)
1137+
// If the directory is the root, use the entire root as the `dir` including
1138+
// the trailing slash if any (`C:\abc` -> `C:\`). Otherwise, strip out the
1139+
// trailing slash (`C:\abc\def` -> `C:\abc`).
1140+
if (startPart > 0 && startPart !== rootEnd)
11361141
ret.dir = path.slice(0, startPart - 1);
1137-
else if (isAbsolute)
1138-
ret.dir = path.slice(0, rootEnd);
1142+
else
1143+
ret.dir = ret.root;
11391144

11401145
return ret;
11411146
},

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

+45-37
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,33 @@ const assert = require('assert');
2525
const path = require('path');
2626

2727
const winPaths = [
28-
'C:\\path\\dir\\index.html',
29-
'C:\\another_path\\DIR\\1\\2\\33\\\\index',
30-
'another_path\\DIR with spaces\\1\\2\\33\\index',
31-
'\\foo\\C:',
32-
'file',
33-
'.\\file',
34-
'C:\\',
35-
'C:',
36-
'\\',
37-
'',
28+
// [path, root]
29+
['C:\\path\\dir\\index.html', 'C:\\'],
30+
['C:\\another_path\\DIR\\1\\2\\33\\\\index', 'C:\\'],
31+
['another_path\\DIR with spaces\\1\\2\\33\\index', ''],
32+
['\\', '\\'],
33+
['\\foo\\C:', '\\'],
34+
['file', ''],
35+
['file:stream', ''],
36+
['.\\file', ''],
37+
['C:', 'C:'],
38+
['C:.', 'C:'],
39+
['C:..', 'C:'],
40+
['C:abc', 'C:'],
41+
['C:\\', 'C:\\'],
42+
['C:\\abc', 'C:\\' ],
43+
['', ''],
3844

3945
// unc
40-
'\\\\server\\share\\file_path',
41-
'\\\\server two\\shared folder\\file path.zip',
42-
'\\\\teela\\admin$\\system32',
43-
'\\\\?\\UNC\\server\\share'
46+
['\\\\server\\share\\file_path', '\\\\server\\share\\'],
47+
['\\\\server two\\shared folder\\file path.zip',
48+
'\\\\server two\\shared folder\\'],
49+
['\\\\teela\\admin$\\system32', '\\\\teela\\admin$\\'],
50+
['\\\\?\\UNC\\server\\share', '\\\\?\\UNC\\']
4451
];
4552

4653
const winSpecialCaseParseTests = [
4754
['/foo/bar', { root: '/' }],
48-
['C:', { root: 'C:', dir: 'C:', base: '' }],
49-
['C:\\', { root: 'C:\\', dir: 'C:\\', base: '' }]
5055
];
5156

5257
const winSpecialCaseFormatTests = [
@@ -60,26 +65,27 @@ const winSpecialCaseFormatTests = [
6065
];
6166

6267
const unixPaths = [
63-
'/home/user/dir/file.txt',
64-
'/home/user/a dir/another File.zip',
65-
'/home/user/a dir//another&File.',
66-
'/home/user/a$$$dir//another File.zip',
67-
'user/dir/another File.zip',
68-
'file',
69-
'.\\file',
70-
'./file',
71-
'C:\\foo',
72-
'/',
73-
'',
74-
'.',
75-
'..',
76-
'/foo',
77-
'/foo.',
78-
'/foo.bar',
79-
'/.',
80-
'/.foo',
81-
'/.foo.bar',
82-
'/foo/bar.baz',
68+
// [path, root]
69+
['/home/user/dir/file.txt', '/'],
70+
['/home/user/a dir/another File.zip', '/'],
71+
['/home/user/a dir//another&File.', '/'],
72+
['/home/user/a$$$dir//another File.zip', '/'],
73+
['user/dir/another File.zip', ''],
74+
['file', ''],
75+
['.\\file', ''],
76+
['./file', ''],
77+
['C:\\foo', ''],
78+
['/', '/'],
79+
['', ''],
80+
['.', ''],
81+
['..', ''],
82+
['/foo', '/'],
83+
['/foo.', '/'],
84+
['/foo.bar', '/'],
85+
['/.', '/'],
86+
['/.foo', '/'],
87+
['/.foo.bar', '/'],
88+
['/foo/bar.baz', '/']
8389
];
8490

8591
const unixSpecialCaseFormatTests = [
@@ -182,14 +188,16 @@ function checkErrors(path) {
182188
}
183189

184190
function checkParseFormat(path, paths) {
185-
paths.forEach(function(element) {
191+
paths.forEach(function([element, root]) {
186192
const output = path.parse(element);
187193
assert.strictEqual(typeof output.root, 'string');
188194
assert.strictEqual(typeof output.dir, 'string');
189195
assert.strictEqual(typeof output.base, 'string');
190196
assert.strictEqual(typeof output.ext, 'string');
191197
assert.strictEqual(typeof output.name, 'string');
192198
assert.strictEqual(path.format(output), element);
199+
assert.strictEqual(output.root, root);
200+
assert(output.dir.startsWith(output.root));
193201
assert.strictEqual(output.dir, output.dir ? path.dirname(element) : '');
194202
assert.strictEqual(output.base, path.basename(element));
195203
assert.strictEqual(output.ext, path.extname(element));

test/parallel/test-path.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ assert.strictEqual(path.win32.basename('aaa\\bbb', 'bbb'), 'bbb');
7272
assert.strictEqual(path.win32.basename('aaa\\bbb\\\\\\\\', 'bbb'), 'bbb');
7373
assert.strictEqual(path.win32.basename('aaa\\bbb', 'bb'), 'b');
7474
assert.strictEqual(path.win32.basename('aaa\\bbb', 'b'), 'bb');
75+
assert.strictEqual(path.win32.basename('C:'), '');
76+
assert.strictEqual(path.win32.basename('C:.'), '.');
77+
assert.strictEqual(path.win32.basename('C:\\'), '');
78+
assert.strictEqual(path.win32.basename('C:\\dir\\base.ext'), 'base.ext');
79+
assert.strictEqual(path.win32.basename('C:\\basename.ext'), 'basename.ext');
80+
assert.strictEqual(path.win32.basename('C:basename.ext'), 'basename.ext');
81+
assert.strictEqual(path.win32.basename('C:basename.ext\\'), 'basename.ext');
82+
assert.strictEqual(path.win32.basename('C:basename.ext\\\\'), 'basename.ext');
83+
assert.strictEqual(path.win32.basename('C:foo'), 'foo');
84+
assert.strictEqual(path.win32.basename('file:stream'), 'file:stream');
7585

7686
// On unix a backslash is just treated as any other character.
7787
assert.strictEqual(path.posix.basename('\\dir\\basename.ext'),
@@ -120,6 +130,8 @@ assert.strictEqual(path.win32.dirname('c:foo\\'), 'c:');
120130
assert.strictEqual(path.win32.dirname('c:foo\\bar'), 'c:foo');
121131
assert.strictEqual(path.win32.dirname('c:foo\\bar\\'), 'c:foo');
122132
assert.strictEqual(path.win32.dirname('c:foo\\bar\\baz'), 'c:foo\\bar');
133+
assert.strictEqual(path.win32.dirname('file:stream'), '.');
134+
assert.strictEqual(path.win32.dirname('dir\\file:stream'), 'dir');
123135
assert.strictEqual(path.win32.dirname('\\\\unc\\share'),
124136
'\\\\unc\\share');
125137
assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo'),
@@ -187,6 +199,7 @@ assert.strictEqual(path.win32.dirname('foo'), '.');
187199
['file./', '.'],
188200
['file.//', '.'],
189201
].forEach((test) => {
202+
const expected = test[1];
190203
[path.posix.extname, path.win32.extname].forEach((extname) => {
191204
let input = test[0];
192205
let os;
@@ -197,12 +210,19 @@ assert.strictEqual(path.win32.dirname('foo'), '.');
197210
os = 'posix';
198211
}
199212
const actual = extname(input);
200-
const expected = test[1];
201213
const message = `path.${os}.extname(${JSON.stringify(input)})\n expect=${
202214
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
203215
if (actual !== expected)
204216
failures.push(`\n${message}`);
205217
});
218+
{
219+
const input = `C:${test[0].replace(slashRE, '\\')}`;
220+
const actual = path.win32.extname(input);
221+
const message = `path.win32.extname(${JSON.stringify(input)})\n expect=${
222+
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
223+
if (actual !== expected)
224+
failures.push(`\n${message}`);
225+
}
206226
});
207227
assert.strictEqual(failures.length, 0, failures.join(''));
208228

@@ -406,6 +426,12 @@ assert.strictEqual(path.win32.normalize('a//b//.'), 'a\\b');
406426
assert.strictEqual(path.win32.normalize('//server/share/dir/file.ext'),
407427
'\\\\server\\share\\dir\\file.ext');
408428
assert.strictEqual(path.win32.normalize('/a/b/c/../../../x/y/z'), '\\x\\y\\z');
429+
assert.strictEqual(path.win32.normalize('C:'), 'C:.');
430+
assert.strictEqual(path.win32.normalize('C:..\\abc'), 'C:..\\abc');
431+
assert.strictEqual(path.win32.normalize('C:..\\..\\abc\\..\\def'),
432+
'C:..\\..\\def');
433+
assert.strictEqual(path.win32.normalize('C:\\.'), 'C:\\');
434+
assert.strictEqual(path.win32.normalize('file:stream'), 'file:stream');
409435

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

0 commit comments

Comments
 (0)