Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v6.x backport] path.win32 related fixes and tests #14787

Closed
wants to merge 3 commits into from
Closed
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
55 changes: 31 additions & 24 deletions lib/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,9 @@ const win32 = {
}
}
} else if (code === 47/*/*/ || code === 92/*\*/) {
return path[0];
// `path` contains just a path separator, exit early to avoid
// unnecessary work
return path;
}

for (var i = len - 1; i >= offset; --i) {
Expand Down Expand Up @@ -885,14 +887,28 @@ const win32 = {

extname: function extname(path) {
assertPath(path);
var start = 0;
var startDot = -1;
var startPart = 0;
var end = -1;
var matchedSlash = true;
// Track the state of characters (if any) we see before our first dot and
// after any path separator we find
var preDotState = 0;
for (var i = path.length - 1; i >= 0; --i) {

// Check for a drive letter prefix so as not to mistake the following
// path separator as an extra separator at the end of the path that can be
// disregarded
if (path.length >= 2) {
const code = path.charCodeAt(0);
if (path.charCodeAt(1) === 58/*:*/ &&
((code >= 65/*A*/ && code <= 90/*Z*/) ||
(code >= 97/*a*/ && code <= 122/*z*/))) {
start = startPart = 2;
}
}

for (var i = path.length - 1; i >= start; --i) {
const code = path.charCodeAt(i);
if (code === 47/*/*/ || code === 92/*\*/) {
// If we reached a path separator that was not part of a set of path
Expand Down Expand Up @@ -956,15 +972,12 @@ const win32 = {
var len = path.length;
var rootEnd = 0;
var code = path.charCodeAt(0);
var isAbsolute = false;

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

isAbsolute = true;

code = path.charCodeAt(1);
rootEnd = 1;
if (code === 47/*/*/ || code === 92/*\*/) {
Expand Down Expand Up @@ -1020,32 +1033,31 @@ const win32 = {
if (len === 3) {
// `path` contains just a drive root, exit early to avoid
// unnecessary work
ret.root = ret.dir = path.slice(0, 3);
ret.root = ret.dir = path;
return ret;
}
isAbsolute = true;
rootEnd = 3;
}
} else {
// `path` contains just a drive root, exit early to avoid
// unnecessary work
ret.root = ret.dir = path.slice(0, 2);
ret.root = ret.dir = path;
return ret;
}
}
}
} else if (code === 47/*/*/ || code === 92/*\*/) {
// `path` contains just a path separator, exit early to avoid
// unnecessary work
ret.root = ret.dir = path[0];
ret.root = ret.dir = path;
return ret;
}

if (rootEnd > 0)
ret.root = path.slice(0, rootEnd);

var startDot = -1;
var startPart = 0;
var startPart = rootEnd;
var end = -1;
var matchedSlash = true;
var i = path.length - 1;
Expand Down Expand Up @@ -1094,26 +1106,21 @@ const win32 = {
startDot === end - 1 &&
startDot === startPart + 1)) {
if (end !== -1) {
if (startPart === 0 && isAbsolute)
ret.base = ret.name = path.slice(rootEnd, end);
else
ret.base = ret.name = path.slice(startPart, end);
ret.base = ret.name = path.slice(startPart, end);
}
} else {
if (startPart === 0 && isAbsolute) {
ret.name = path.slice(rootEnd, startDot);
ret.base = path.slice(rootEnd, end);
} else {
ret.name = path.slice(startPart, startDot);
ret.base = path.slice(startPart, end);
}
ret.name = path.slice(startPart, startDot);
ret.base = path.slice(startPart, end);
ret.ext = path.slice(startDot, end);
}

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

return ret;
},
Expand Down
80 changes: 46 additions & 34 deletions test/parallel/test-path-parse-format.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,33 @@ const assert = require('assert');
const path = require('path');

const winPaths = [
'C:\\path\\dir\\index.html',
'C:\\another_path\\DIR\\1\\2\\33\\\\index',
'another_path\\DIR with spaces\\1\\2\\33\\index',
'\\foo\\C:',
'file',
'.\\file',
'C:\\',
'',
// [path, root]
['C:\\path\\dir\\index.html', 'C:\\'],
['C:\\another_path\\DIR\\1\\2\\33\\\\index', 'C:\\'],
['another_path\\DIR with spaces\\1\\2\\33\\index', ''],
['\\', '\\'],
['\\foo\\C:', '\\'],
['file', ''],
['file:stream', ''],
['.\\file', ''],
['C:', 'C:'],
['C:.', 'C:'],
['C:..', 'C:'],
['C:abc', 'C:'],
['C:\\', 'C:\\'],
['C:\\abc', 'C:\\' ],
['', ''],

// unc
'\\\\server\\share\\file_path',
'\\\\server two\\shared folder\\file path.zip',
'\\\\teela\\admin$\\system32',
'\\\\?\\UNC\\server\\share'
['\\\\server\\share\\file_path', '\\\\server\\share\\'],
['\\\\server two\\shared folder\\file path.zip',
'\\\\server two\\shared folder\\'],
['\\\\teela\\admin$\\system32', '\\\\teela\\admin$\\'],
['\\\\?\\UNC\\server\\share', '\\\\?\\UNC\\']
];

const winSpecialCaseParseTests = [
['/foo/bar', {root: '/'}]
['/foo/bar', { root: '/' }],
];

const winSpecialCaseFormatTests = [
Expand All @@ -35,26 +44,27 @@ const winSpecialCaseFormatTests = [
];

const unixPaths = [
'/home/user/dir/file.txt',
'/home/user/a dir/another File.zip',
'/home/user/a dir//another&File.',
'/home/user/a$$$dir//another File.zip',
'user/dir/another File.zip',
'file',
'.\\file',
'./file',
'C:\\foo',
'/',
'',
'.',
'..',
'/foo',
'/foo.',
'/foo.bar',
'/.',
'/.foo',
'/.foo.bar',
'/foo/bar.baz',
// [path, root]
['/home/user/dir/file.txt', '/'],
['/home/user/a dir/another File.zip', '/'],
['/home/user/a dir//another&File.', '/'],
['/home/user/a$$$dir//another File.zip', '/'],
['user/dir/another File.zip', ''],
['file', ''],
['.\\file', ''],
['./file', ''],
['C:\\foo', ''],
['/', '/'],
['', ''],
['.', ''],
['..', ''],
['/foo', '/'],
['/foo.', '/'],
['/foo.bar', '/'],
['/.', '/'],
['/.foo', '/'],
['/.foo.bar', '/'],
['/foo/bar.baz', '/']
];

const unixSpecialCaseFormatTests = [
Expand Down Expand Up @@ -165,14 +175,16 @@ function checkErrors(path) {
}

function checkParseFormat(path, paths) {
paths.forEach(function(element) {
paths.forEach(function([element, root]) {
const output = path.parse(element);
assert.strictEqual(typeof output.root, 'string');
assert.strictEqual(typeof output.dir, 'string');
assert.strictEqual(typeof output.base, 'string');
assert.strictEqual(typeof output.ext, 'string');
assert.strictEqual(typeof output.name, 'string');
assert.strictEqual(path.format(output), element);
assert.strictEqual(output.root, root);
assert(output.dir.startsWith(output.root));
assert.strictEqual(output.dir, output.dir ? path.dirname(element) : '');
assert.strictEqual(output.base, path.basename(element));
assert.strictEqual(output.ext, path.extname(element));
Expand Down
28 changes: 27 additions & 1 deletion test/parallel/test-path.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ assert.strictEqual(path.win32.basename('aaa\\bbb', 'bbb'), 'bbb');
assert.strictEqual(path.win32.basename('aaa\\bbb\\\\\\\\', 'bbb'), 'bbb');
assert.strictEqual(path.win32.basename('aaa\\bbb', 'bb'), 'b');
assert.strictEqual(path.win32.basename('aaa\\bbb', 'b'), 'bb');
assert.strictEqual(path.win32.basename('C:'), '');
assert.strictEqual(path.win32.basename('C:.'), '.');
assert.strictEqual(path.win32.basename('C:\\'), '');
assert.strictEqual(path.win32.basename('C:\\dir\\base.ext'), 'base.ext');
assert.strictEqual(path.win32.basename('C:\\basename.ext'), 'basename.ext');
assert.strictEqual(path.win32.basename('C:basename.ext'), 'basename.ext');
assert.strictEqual(path.win32.basename('C:basename.ext\\'), 'basename.ext');
assert.strictEqual(path.win32.basename('C:basename.ext\\\\'), 'basename.ext');
assert.strictEqual(path.win32.basename('C:foo'), 'foo');
assert.strictEqual(path.win32.basename('file:stream'), 'file:stream');

// On unix a backslash is just treated as any other character.
assert.strictEqual(path.posix.basename('\\dir\\basename.ext'),
Expand Down Expand Up @@ -99,6 +109,8 @@ assert.strictEqual(path.win32.dirname('c:foo\\'), 'c:');
assert.strictEqual(path.win32.dirname('c:foo\\bar'), 'c:foo');
assert.strictEqual(path.win32.dirname('c:foo\\bar\\'), 'c:foo');
assert.strictEqual(path.win32.dirname('c:foo\\bar\\baz'), 'c:foo\\bar');
assert.strictEqual(path.win32.dirname('file:stream'), '.');
assert.strictEqual(path.win32.dirname('dir\\file:stream'), 'dir');
assert.strictEqual(path.win32.dirname('\\\\unc\\share'),
'\\\\unc\\share');
assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo'),
Expand Down Expand Up @@ -166,6 +178,7 @@ assert.strictEqual(path.win32.dirname('foo'), '.');
['file./', '.'],
['file.//', '.'],
].forEach((test) => {
const expected = test[1];
[path.posix.extname, path.win32.extname].forEach((extname) => {
let input = test[0];
let os;
Expand All @@ -176,12 +189,19 @@ assert.strictEqual(path.win32.dirname('foo'), '.');
os = 'posix';
}
const actual = extname(input);
const expected = test[1];
const message = `path.${os}.extname(${JSON.stringify(input)})\n expect=${
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
if (actual !== expected)
failures.push(`\n${message}`);
});
{
const input = `C:${test[0].replace(slashRE, '\\')}`;
const actual = path.win32.extname(input);
const message = `path.win32.extname(${JSON.stringify(input)})\n expect=${
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
if (actual !== expected)
failures.push(`\n${message}`);
}
});
assert.strictEqual(failures.length, 0, failures.join(''));

Expand Down Expand Up @@ -385,6 +405,12 @@ assert.strictEqual(path.win32.normalize('a//b//.'), 'a\\b');
assert.strictEqual(path.win32.normalize('//server/share/dir/file.ext'),
'\\\\server\\share\\dir\\file.ext');
assert.strictEqual(path.win32.normalize('/a/b/c/../../../x/y/z'), '\\x\\y\\z');
assert.strictEqual(path.win32.normalize('C:'), 'C:.');
assert.strictEqual(path.win32.normalize('C:..\\abc'), 'C:..\\abc');
assert.strictEqual(path.win32.normalize('C:..\\..\\abc\\..\\def'),
'C:..\\..\\def');
assert.strictEqual(path.win32.normalize('C:\\.'), 'C:\\');
assert.strictEqual(path.win32.normalize('file:stream'), 'file:stream');

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