From bc9388342f59ed5c69d69c7095e5a17fcbd80ba8 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Wed, 21 Nov 2012 01:00:45 +0100 Subject: [PATCH] windows: fix normalization of UNC paths --- lib/path.js | 38 +++++++++++++++++------ test/simple/test-path.js | 66 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 10 deletions(-) diff --git a/lib/path.js b/lib/path.js index 7e521f20de..139d04aae8 100644 --- a/lib/path.js +++ b/lib/path.js @@ -59,7 +59,7 @@ if (isWindows) { // Regex to split a windows path into three parts: [*, device, slash, // tail] windows-only var splitDeviceRe = - /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?([\\\/])?([\s\S]*?)$/; + /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; // Regex to split the tail part of the above into [*, dir, basename, ext] var splitTailRe = @@ -80,6 +80,10 @@ if (isWindows) { return [device, dir, basename, ext]; }; + var normalizeUNCRoot = function(device) { + return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\'); + } + // path.resolve([from ...], to) // windows version exports.resolve = function() { @@ -138,8 +142,11 @@ if (isWindows) { } } - // Replace slashes (in UNC share name) by backslashes - resolvedDevice = resolvedDevice.replace(/\//g, '\\'); + // Convert slashes to backslashes when `resolvedDevice` points to an UNC + // root. Also squash multiple slashes into a single one where appropriate. + if (isUnc) { + resolvedDevice = normalizeUNCRoot(resolvedDevice); + } // At this point the path should be resolved to a full absolute path, // but handle relative paths to be safe (might happen when process.cwd() @@ -180,7 +187,10 @@ if (isWindows) { } // Convert slashes to backslashes when `device` points to an UNC root. - device = device.replace(/\//g, '\\'); + // Also squash multiple slashes into a single one where appropriate. + if (isUnc) { + device = normalizeUNCRoot(device); + } return device + (isAbsolute ? '\\' : '') + tail; }; @@ -194,11 +204,21 @@ if (isWindows) { var paths = Array.prototype.filter.call(arguments, f); var joined = paths.join('\\'); - // Make sure that the joined path doesn't start with two slashes - // - it will be mistaken for an unc path by normalize() - - // unless the paths[0] also starts with two slashes - if (/^[\\\/]{2}/.test(joined) && !/^[\\\/]{2}/.test(paths[0])) { - joined = joined.substr(1); + // Make sure that the joined path doesn't start with two slashes, because + // normalize() will mistake it for an UNC path then. + // + // This step is skipped when it is very clear that the user actually + // intended to point at an UNC path. This is assumed when the first + // non-empty string arguments starts with exactly two slashes followed by + // at least one more non-slash character. + // + // Note that for normalize() to treat a path as an UNC path it needs to + // have at least 2 components, so we don't filter for that here. + // This means that the user can use join to construct UNC paths from + // a server name and a share name; for example: + // path.join('//server', 'share') -> '\\\\server\\share\') + if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) { + joined = joined.replace(/^[\\\/]{2,}/, '\\'); } return exports.normalize(joined); diff --git a/test/simple/test-path.js b/test/simple/test-path.js index ca0afc26d5..1c03887e14 100644 --- a/test/simple/test-path.js +++ b/test/simple/test-path.js @@ -172,9 +172,67 @@ var joinTests = [[' ', '.'], ' '], [[' ', '/'], ' /'], [[' ', ''], ' '], + [['/', 'foo'], '/foo'], + [['/', '/foo'], '/foo'], + [['/', '//foo'], '/foo'], + [['/', '', '/foo'], '/foo'], + [['', '/', 'foo'], '/foo'], + [['', '/', '/foo'], '/foo'], // filtration of non-strings. [['x', true, 7, 'y', null, {}], 'x/y'] ]; + +// Windows-specific join tests +if (isWindows) { + joinTests = joinTests.concat( + [// UNC path expected + [['//foo/bar'], '//foo/bar/'], + [['\\/foo/bar'], '//foo/bar/'], + [['\\\\foo/bar'], '//foo/bar/'], + // UNC path expected - server and share separate + [['//foo', 'bar'], '//foo/bar/'], + [['//foo/', 'bar'], '//foo/bar/'], + [['//foo', '/bar'], '//foo/bar/'], + // UNC path expected - questionable + [['//foo', '', 'bar'], '//foo/bar/'], + [['//foo/', '', 'bar'], '//foo/bar/'], + [['//foo/', '', '/bar'], '//foo/bar/'], + // UNC path expected - even more questionable + [['', '//foo', 'bar'], '//foo/bar/'], + [['', '//foo/', 'bar'], '//foo/bar/'], + [['', '//foo/', '/bar'], '//foo/bar/'], + // No UNC path expected (no double slash in first component) + [['\\', 'foo/bar'], '/foo/bar'], + [['\\', '/foo/bar'], '/foo/bar'], + [['', '/', '/foo/bar'], '/foo/bar'], + // No UNC path expected (no non-slashes in first component - questionable) + [['//', 'foo/bar'], '/foo/bar'], + [['//', '/foo/bar'], '/foo/bar'], + [['\\\\', '/', '/foo/bar'], '/foo/bar'], + [['//'], '/'], + // No UNC path expected (share name missing - questionable). + [['//foo'], '/foo'], + [['//foo/'], '/foo/'], + [['//foo', '/'], '/foo/'], + [['//foo', '', '/'], '/foo/'], + // No UNC path expected (too many leading slashes - questionable) + [['///foo/bar'], '/foo/bar'], + [['////foo', 'bar'], '/foo/bar'], + [['\\\\\\/foo/bar'], '/foo/bar'], + // Drive-relative vs drive-absolute paths. This merely describes the + // status quo, rather than being obviously right + [['c:'], 'c:.'], + [['c:.'], 'c:.'], + [['c:', ''], 'c:.'], + [['', 'c:'], 'c:.'], + [['c:.', '/'], 'c:./'], + [['c:.', 'file'], 'c:file'], + [['c:', '/'], 'c:/'], + [['c:', 'file'], 'c:/file'] + ]); +} + +// Run the join tests. joinTests.forEach(function(test) { var actual = path.join.apply(path, test[0]); var expected = isWindows ? test[1].replace(/\//g, '\\') : test[1]; @@ -215,7 +273,13 @@ if (isWindows) { [['c:/ignore', 'c:/some/file'], 'c:\\some\\file'], [['d:/ignore', 'd:some/dir//'], 'd:\\ignore\\some\\dir'], [['.'], process.cwd()], - [['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative']]; + [['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative'], + [['c:/', '//'], 'c:\\'], + [['c:/', '//dir'], 'c:\\dir'], + [['c:/', '//server/share'], '\\\\server\\share\\'], + [['c:/', '//server//share'], '\\\\server\\share\\'], + [['c:/', '///some//dir'], 'c:\\some\\dir'] + ]; } else { // Posix var resolveTests =