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: https://github.com/nodejs/node/pull/14440
Fixes: https://github.com/nodejs/node/issues/14405
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
This commit is contained in:
Timothy Gu 2017-07-24 08:39:02 +08:00
parent 85add2bf99
commit 2791761eda
No known key found for this signature in database
GPG Key ID: 7FE6B095B582B0D4
3 changed files with 97 additions and 58 deletions

View File

@ -908,6 +908,7 @@ const win32 = {
extname: function extname(path) { extname: function extname(path) {
assertPath(path); assertPath(path);
var start = 0;
var startDot = -1; var startDot = -1;
var startPart = 0; var startPart = 0;
var end = -1; var end = -1;
@ -915,7 +916,20 @@ const win32 = {
// Track the state of characters (if any) we see before our first dot and // Track the state of characters (if any) we see before our first dot and
// after any path separator we find // after any path separator we find
var preDotState = 0; 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); const code = path.charCodeAt(i);
if (code === 47/*/*/ || code === 92/*\*/) { if (code === 47/*/*/ || code === 92/*\*/) {
// If we reached a path separator that was not part of a set of path // If we reached a path separator that was not part of a set of path
@ -978,15 +992,12 @@ const win32 = {
var len = path.length; var len = path.length;
var rootEnd = 0; var rootEnd = 0;
var code = path.charCodeAt(0); var code = path.charCodeAt(0);
var isAbsolute = false;
// Try to match a root // Try to match a root
if (len > 1) { if (len > 1) {
if (code === 47/*/*/ || code === 92/*\*/) { if (code === 47/*/*/ || code === 92/*\*/) {
// Possible UNC root // Possible UNC root
isAbsolute = true;
code = path.charCodeAt(1); code = path.charCodeAt(1);
rootEnd = 1; rootEnd = 1;
if (code === 47/*/*/ || code === 92/*\*/) { if (code === 47/*/*/ || code === 92/*\*/) {
@ -1045,7 +1056,6 @@ const win32 = {
ret.root = ret.dir = path; ret.root = ret.dir = path;
return ret; return ret;
} }
isAbsolute = true;
rootEnd = 3; rootEnd = 3;
} }
} else { } else {
@ -1067,7 +1077,7 @@ const win32 = {
ret.root = path.slice(0, rootEnd); ret.root = path.slice(0, rootEnd);
var startDot = -1; var startDot = -1;
var startPart = 0; var startPart = rootEnd;
var end = -1; var end = -1;
var matchedSlash = true; var matchedSlash = true;
var i = path.length - 1; var i = path.length - 1;
@ -1116,26 +1126,21 @@ const win32 = {
startDot === end - 1 && startDot === end - 1 &&
startDot === startPart + 1)) { startDot === startPart + 1)) {
if (end !== -1) { if (end !== -1) {
if (startPart === 0 && isAbsolute) ret.base = ret.name = path.slice(startPart, end);
ret.base = ret.name = path.slice(rootEnd, end);
else
ret.base = ret.name = path.slice(startPart, end);
} }
} else { } else {
if (startPart === 0 && isAbsolute) { ret.name = path.slice(startPart, startDot);
ret.name = path.slice(rootEnd, startDot); ret.base = path.slice(startPart, end);
ret.base = path.slice(rootEnd, end);
} else {
ret.name = path.slice(startPart, startDot);
ret.base = path.slice(startPart, end);
}
ret.ext = path.slice(startDot, 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); ret.dir = path.slice(0, startPart - 1);
else if (isAbsolute) else
ret.dir = path.slice(0, rootEnd); ret.dir = ret.root;
return ret; return ret;
}, },

View File

@ -25,28 +25,33 @@ const assert = require('assert');
const path = require('path'); const path = require('path');
const winPaths = [ const winPaths = [
'C:\\path\\dir\\index.html', // [path, root]
'C:\\another_path\\DIR\\1\\2\\33\\\\index', ['C:\\path\\dir\\index.html', 'C:\\'],
'another_path\\DIR with spaces\\1\\2\\33\\index', ['C:\\another_path\\DIR\\1\\2\\33\\\\index', 'C:\\'],
'\\foo\\C:', ['another_path\\DIR with spaces\\1\\2\\33\\index', ''],
'file', ['\\', '\\'],
'.\\file', ['\\foo\\C:', '\\'],
'C:\\', ['file', ''],
'C:', ['file:stream', ''],
'\\', ['.\\file', ''],
'', ['C:', 'C:'],
['C:.', 'C:'],
['C:..', 'C:'],
['C:abc', 'C:'],
['C:\\', 'C:\\'],
['C:\\abc', 'C:\\' ],
['', ''],
// unc // unc
'\\\\server\\share\\file_path', ['\\\\server\\share\\file_path', '\\\\server\\share\\'],
'\\\\server two\\shared folder\\file path.zip', ['\\\\server two\\shared folder\\file path.zip',
'\\\\teela\\admin$\\system32', '\\\\server two\\shared folder\\'],
'\\\\?\\UNC\\server\\share' ['\\\\teela\\admin$\\system32', '\\\\teela\\admin$\\'],
['\\\\?\\UNC\\server\\share', '\\\\?\\UNC\\']
]; ];
const winSpecialCaseParseTests = [ const winSpecialCaseParseTests = [
['/foo/bar', { root: '/' }], ['/foo/bar', { root: '/' }],
['C:', { root: 'C:', dir: 'C:', base: '' }],
['C:\\', { root: 'C:\\', dir: 'C:\\', base: '' }]
]; ];
const winSpecialCaseFormatTests = [ const winSpecialCaseFormatTests = [
@ -60,26 +65,27 @@ const winSpecialCaseFormatTests = [
]; ];
const unixPaths = [ const unixPaths = [
'/home/user/dir/file.txt', // [path, root]
'/home/user/a dir/another File.zip', ['/home/user/dir/file.txt', '/'],
'/home/user/a dir//another&File.', ['/home/user/a dir/another File.zip', '/'],
'/home/user/a$$$dir//another File.zip', ['/home/user/a dir//another&File.', '/'],
'user/dir/another File.zip', ['/home/user/a$$$dir//another File.zip', '/'],
'file', ['user/dir/another File.zip', ''],
'.\\file', ['file', ''],
'./file', ['.\\file', ''],
'C:\\foo', ['./file', ''],
'/', ['C:\\foo', ''],
'', ['/', '/'],
'.', ['', ''],
'..', ['.', ''],
'/foo', ['..', ''],
'/foo.', ['/foo', '/'],
'/foo.bar', ['/foo.', '/'],
'/.', ['/foo.bar', '/'],
'/.foo', ['/.', '/'],
'/.foo.bar', ['/.foo', '/'],
'/foo/bar.baz', ['/.foo.bar', '/'],
['/foo/bar.baz', '/']
]; ];
const unixSpecialCaseFormatTests = [ const unixSpecialCaseFormatTests = [
@ -182,7 +188,7 @@ function checkErrors(path) {
} }
function checkParseFormat(path, paths) { function checkParseFormat(path, paths) {
paths.forEach(function(element) { paths.forEach(function([element, root]) {
const output = path.parse(element); const output = path.parse(element);
assert.strictEqual(typeof output.root, 'string'); assert.strictEqual(typeof output.root, 'string');
assert.strictEqual(typeof output.dir, 'string'); assert.strictEqual(typeof output.dir, 'string');
@ -190,6 +196,8 @@ function checkParseFormat(path, paths) {
assert.strictEqual(typeof output.ext, 'string'); assert.strictEqual(typeof output.ext, 'string');
assert.strictEqual(typeof output.name, 'string'); assert.strictEqual(typeof output.name, 'string');
assert.strictEqual(path.format(output), element); 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.dir, output.dir ? path.dirname(element) : '');
assert.strictEqual(output.base, path.basename(element)); assert.strictEqual(output.base, path.basename(element));
assert.strictEqual(output.ext, path.extname(element)); assert.strictEqual(output.ext, path.extname(element));

View File

@ -72,6 +72,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\\\\\\\\', 'bbb'), 'bbb');
assert.strictEqual(path.win32.basename('aaa\\bbb', 'bb'), 'b'); assert.strictEqual(path.win32.basename('aaa\\bbb', 'bb'), 'b');
assert.strictEqual(path.win32.basename('aaa\\bbb', 'b'), 'bb'); 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. // On unix a backslash is just treated as any other character.
assert.strictEqual(path.posix.basename('\\dir\\basename.ext'), assert.strictEqual(path.posix.basename('\\dir\\basename.ext'),
@ -120,6 +130,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\\'), '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('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'), assert.strictEqual(path.win32.dirname('\\\\unc\\share'),
'\\\\unc\\share'); '\\\\unc\\share');
assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo'), assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo'),
@ -187,6 +199,7 @@ assert.strictEqual(path.win32.dirname('foo'), '.');
['file./', '.'], ['file./', '.'],
['file.//', '.'], ['file.//', '.'],
].forEach((test) => { ].forEach((test) => {
const expected = test[1];
[path.posix.extname, path.win32.extname].forEach((extname) => { [path.posix.extname, path.win32.extname].forEach((extname) => {
let input = test[0]; let input = test[0];
let os; let os;
@ -197,12 +210,19 @@ assert.strictEqual(path.win32.dirname('foo'), '.');
os = 'posix'; os = 'posix';
} }
const actual = extname(input); const actual = extname(input);
const expected = test[1];
const message = `path.${os}.extname(${JSON.stringify(input)})\n expect=${ const message = `path.${os}.extname(${JSON.stringify(input)})\n expect=${
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
if (actual !== expected) if (actual !== expected)
failures.push(`\n${message}`); 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('')); assert.strictEqual(failures.length, 0, failures.join(''));
@ -406,6 +426,12 @@ assert.strictEqual(path.win32.normalize('a//b//.'), 'a\\b');
assert.strictEqual(path.win32.normalize('//server/share/dir/file.ext'), assert.strictEqual(path.win32.normalize('//server/share/dir/file.ext'),
'\\\\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('/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'), assert.strictEqual(path.posix.normalize('./fixtures///b/../b/c.js'),
'fixtures/b/c.js'); 'fixtures/b/c.js');