Skip to content

Commit

Permalink
lib: add UNC support to url.pathToFileURL()
Browse files Browse the repository at this point in the history
Fixes: #34736

PR-URL: #34743
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
  • Loading branch information
mceachen authored and addaleax committed Sep 22, 2020
1 parent 9bcf6fc commit 418ff2d
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 21 deletions.
66 changes: 47 additions & 19 deletions lib/internal/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const { getConstructorOf, removeColors } = require('internal/util');
const {
ERR_ARG_NOT_ITERABLE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_CALLBACK,
ERR_INVALID_FILE_URL_HOST,
ERR_INVALID_FILE_URL_PATH,
Expand Down Expand Up @@ -1369,27 +1370,54 @@ const backslashRegEx = /\\/g;
const newlineRegEx = /\n/g;
const carriageReturnRegEx = /\r/g;
const tabRegEx = /\t/g;

function encodePathChars(filepath) {
if (filepath.includes('%'))
filepath = filepath.replace(percentRegEx, '%25');
// In posix, backslash is a valid character in paths:
if (!isWindows && filepath.includes('\\'))
filepath = filepath.replace(backslashRegEx, '%5C');
if (filepath.includes('\n'))
filepath = filepath.replace(newlineRegEx, '%0A');
if (filepath.includes('\r'))
filepath = filepath.replace(carriageReturnRegEx, '%0D');
if (filepath.includes('\t'))
filepath = filepath.replace(tabRegEx, '%09');
return filepath;
}

function pathToFileURL(filepath) {
let resolved = path.resolve(filepath);
// path.resolve strips trailing slashes so we must add them back
const filePathLast = filepath.charCodeAt(filepath.length - 1);
if ((filePathLast === CHAR_FORWARD_SLASH ||
(isWindows && filePathLast === CHAR_BACKWARD_SLASH)) &&
resolved[resolved.length - 1] !== path.sep)
resolved += '/';
const outURL = new URL('file://');
if (resolved.includes('%'))
resolved = resolved.replace(percentRegEx, '%25');
// In posix, "/" is a valid character in paths
if (!isWindows && resolved.includes('\\'))
resolved = resolved.replace(backslashRegEx, '%5C');
if (resolved.includes('\n'))
resolved = resolved.replace(newlineRegEx, '%0A');
if (resolved.includes('\r'))
resolved = resolved.replace(carriageReturnRegEx, '%0D');
if (resolved.includes('\t'))
resolved = resolved.replace(tabRegEx, '%09');
outURL.pathname = resolved;
if (isWindows && filepath.startsWith('\\\\')) {
// UNC path format: \\server\share\resource
const paths = filepath.split('\\');
if (paths.length <= 3) {
throw new ERR_INVALID_ARG_VALUE(
'filepath',
filepath,
'Missing UNC resource path'
);
}
const hostname = paths[2];
if (hostname.length === 0) {
throw new ERR_INVALID_ARG_VALUE(
'filepath',
filepath,
'Empty UNC servername'
);
}
outURL.hostname = domainToASCII(hostname);
outURL.pathname = encodePathChars(paths.slice(3).join('/'));
} else {
let resolved = path.resolve(filepath);
// path.resolve strips trailing slashes so we must add them back
const filePathLast = filepath.charCodeAt(filepath.length - 1);
if ((filePathLast === CHAR_FORWARD_SLASH ||
(isWindows && filePathLast === CHAR_BACKWARD_SLASH)) &&
resolved[resolved.length - 1] !== path.sep)
resolved += '/';
outURL.pathname = encodePathChars(resolved);
}
return outURL;
}

Expand Down
4 changes: 3 additions & 1 deletion test/parallel/test-url-fileurltopath.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ assert.throws(() => url.fileURLToPath('https://a/b/c'), {
// Euro sign (BMP code point)
{ path: 'C:\\€', fileURL: 'file:///C:/%E2%82%AC' },
// Rocket emoji (non-BMP code point)
{ path: 'C:\\🚀', fileURL: 'file:///C:/%F0%9F%9A%80' }
{ path: 'C:\\🚀', fileURL: 'file:///C:/%F0%9F%9A%80' },
// UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows)
{ path: '\\\\nas\\My Docs\\File.doc', fileURL: 'file://nas/My%20Docs/File.doc' },
];
} else {
testCases = [
Expand Down
24 changes: 23 additions & 1 deletion test/parallel/test-url-pathtofileurl.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,26 @@ const url = require('url');
assert.ok(fileURL.includes('%25'));
}

{
if (isWindows) {
// UNC path: \\server\share\resource

// Missing server:
assert.throws(() => url.pathToFileURL('\\\\\\no-server'), {
code: 'ERR_INVALID_ARG_VALUE'
});

// Missing share or resource:
assert.throws(() => url.pathToFileURL('\\\\host'), {
code: 'ERR_INVALID_ARG_VALUE'
});
} else {
// UNC paths on posix are considered a single path that has backslashes:
const fileURL = url.pathToFileURL('\\\\nas\\share\\path.txt').href;
assert.match(fileURL, /file:\/\/.+%5C%5Cnas%5Cshare%5Cpath\.txt$/);
}
}

{
let testCases;
if (isWindows) {
Expand Down Expand Up @@ -68,7 +88,9 @@ const url = require('url');
// Euro sign (BMP code point)
{ path: 'C:\\€', expected: 'file:///C:/%E2%82%AC' },
// Rocket emoji (non-BMP code point)
{ path: 'C:\\🚀', expected: 'file:///C:/%F0%9F%9A%80' }
{ path: 'C:\\🚀', expected: 'file:///C:/%F0%9F%9A%80' },
// UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows)
{ path: '\\\\nas\\My Docs\\File.doc', expected: 'file://nas/My%20Docs/File.doc' }
];
} else {
testCases = [
Expand Down

0 comments on commit 418ff2d

Please sign in to comment.