Skip to content

Commit b41ae98

Browse files
TimothyGuaddaleax
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. PR-URL: #14440 Fixes: #14405 Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: Tobias Nießen <[email protected]>
1 parent d90a5e0 commit b41ae98

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
@@ -979,15 +993,12 @@ const win32 = {
979993
var len = path.length;
980994
var rootEnd = 0;
981995
var code = path.charCodeAt(0);
982-
var isAbsolute = false;
983996

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

989-
isAbsolute = true;
990-
9911002
code = path.charCodeAt(1);
9921003
rootEnd = 1;
9931004
if (code === 47/*/*/ || code === 92/*\*/) {
@@ -1046,7 +1057,6 @@ const win32 = {
10461057
ret.root = ret.dir = path;
10471058
return ret;
10481059
}
1049-
isAbsolute = true;
10501060
rootEnd = 3;
10511061
}
10521062
} else {
@@ -1068,7 +1078,7 @@ const win32 = {
10681078
ret.root = path.slice(0, rootEnd);
10691079

10701080
var startDot = -1;
1071-
var startPart = 0;
1081+
var startPart = rootEnd;
10721082
var end = -1;
10731083
var matchedSlash = true;
10741084
var i = path.length - 1;
@@ -1117,26 +1127,21 @@ const win32 = {
11171127
startDot === end - 1 &&
11181128
startDot === startPart + 1)) {
11191129
if (end !== -1) {
1120-
if (startPart === 0 && isAbsolute)
1121-
ret.base = ret.name = path.slice(rootEnd, end);
1122-
else
1123-
ret.base = ret.name = path.slice(startPart, end);
1130+
ret.base = ret.name = path.slice(startPart, end);
11241131
}
11251132
} else {
1126-
if (startPart === 0 && isAbsolute) {
1127-
ret.name = path.slice(rootEnd, startDot);
1128-
ret.base = path.slice(rootEnd, end);
1129-
} else {
1130-
ret.name = path.slice(startPart, startDot);
1131-
ret.base = path.slice(startPart, end);
1132-
}
1133+
ret.name = path.slice(startPart, startDot);
1134+
ret.base = path.slice(startPart, end);
11331135
ret.ext = path.slice(startDot, end);
11341136
}
11351137

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

11411146
return ret;
11421147
},

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 = [
@@ -190,14 +196,16 @@ function checkErrors(path) {
190196
}
191197

192198
function checkParseFormat(path, paths) {
193-
paths.forEach(function(element) {
199+
paths.forEach(function([element, root]) {
194200
const output = path.parse(element);
195201
assert.strictEqual(typeof output.root, 'string');
196202
assert.strictEqual(typeof output.dir, 'string');
197203
assert.strictEqual(typeof output.base, 'string');
198204
assert.strictEqual(typeof output.ext, 'string');
199205
assert.strictEqual(typeof output.name, 'string');
200206
assert.strictEqual(path.format(output), element);
207+
assert.strictEqual(output.root, root);
208+
assert(output.dir.startsWith(output.root));
201209
assert.strictEqual(output.dir, output.dir ? path.dirname(element) : '');
202210
assert.strictEqual(output.base, path.basename(element));
203211
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)