Skip to content

Commit bfa457e

Browse files
committed
lib, url: fix #51609 by adding a win argument
1 parent 2cd3073 commit bfa457e

File tree

4 files changed

+250
-213
lines changed

4 files changed

+250
-213
lines changed

doc/api/url.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,13 +1151,22 @@ console.log(url.domainToUnicode('xn--iñvalid.com'));
11511151
// Prints an empty string
11521152
```
11531153

1154-
### `url.fileURLToPath(url)`
1154+
### `url.fileURLToPath(url [, options])`
11551155

11561156
<!-- YAML
11571157
added: v10.12.0
1158+
changes:
1159+
- version: REPLACEME
1160+
pr-url: https://github.com/nodejs/node/pull/52509
1161+
description: The `options` argument can now be used to
1162+
determine how to parse the `path` argument.
11581163
-->
11591164

11601165
* `url` {URL | string} The file URL string or URL object to convert to a path.
1166+
* `options` {Object}
1167+
* `windows` {boolean|undefined} `true` if the `path` should be
1168+
treated as a windows filepath, `false` otherwise.
1169+
**Default:** `undefined`.
11611170
* Returns: {string} The fully-resolved platform-specific Node.js file path.
11621171

11631172
This function ensures the correct decodings of percent-encoded characters as
@@ -1251,13 +1260,22 @@ console.log(url.format(myURL, { fragment: false, unicode: true, auth: false }));
12511260
// Prints 'https://測試/?abc'
12521261
```
12531262
1254-
### `url.pathToFileURL(path)`
1263+
### `url.pathToFileURL(path[, options])`
12551264
12561265
<!-- YAML
12571266
added: v10.12.0
1267+
changes:
1268+
- version: REPLACEME
1269+
pr-url: https://github.com/nodejs/node/pull/52509
1270+
description: The `options` argument can now be used to
1271+
determine how to parse the `path` argument.
12581272
-->
12591273
12601274
* `path` {string} The path to convert to a File URL.
1275+
* `options` {Object}
1276+
* `windows` {boolean|undefined} `true` if the `path` should be
1277+
treated as a windows filepath, `false` otherwise.
1278+
**Default:** `undefined`.
12611279
* Returns: {URL} The file URL object.
12621280
12631281
This function ensures that `path` is resolved absolutely, and that the URL

lib/internal/url.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,14 +1460,14 @@ function getPathFromURLPosix(url) {
14601460
return decodeURIComponent(pathname);
14611461
}
14621462

1463-
function fileURLToPath(path) {
1463+
function fileURLToPath(path, { windows } = {}) {
14641464
if (typeof path === 'string')
14651465
path = new URL(path);
14661466
else if (!isURL(path))
14671467
throw new ERR_INVALID_ARG_TYPE('path', ['string', 'URL'], path);
14681468
if (path.protocol !== 'file:')
14691469
throw new ERR_INVALID_URL_SCHEME('file');
1470-
return isWindows ? getPathFromURLWin32(path) : getPathFromURLPosix(path);
1470+
return (windows ?? isWindows) ? getPathFromURLWin32(path) : getPathFromURLPosix(path);
14711471
}
14721472

14731473
// The following characters are percent-encoded when converting from file path
@@ -1489,11 +1489,11 @@ const tabRegEx = /\t/g;
14891489
const questionRegex = /\?/g;
14901490
const hashRegex = /#/g;
14911491

1492-
function encodePathChars(filepath) {
1492+
function encodePathChars(filepath, { windows } = {}) {
14931493
if (StringPrototypeIndexOf(filepath, '%') !== -1)
14941494
filepath = RegExpPrototypeSymbolReplace(percentRegEx, filepath, '%25');
14951495
// In posix, backslash is a valid character in paths:
1496-
if (!isWindows && StringPrototypeIndexOf(filepath, '\\') !== -1)
1496+
if (!(windows ?? isWindows) && StringPrototypeIndexOf(filepath, '\\') !== -1)
14971497
filepath = RegExpPrototypeSymbolReplace(backslashRegEx, filepath, '%5C');
14981498
if (StringPrototypeIndexOf(filepath, '\n') !== -1)
14991499
filepath = RegExpPrototypeSymbolReplace(newlineRegEx, filepath, '%0A');
@@ -1504,8 +1504,8 @@ function encodePathChars(filepath) {
15041504
return filepath;
15051505
}
15061506

1507-
function pathToFileURL(filepath) {
1508-
if (isWindows && StringPrototypeStartsWith(filepath, '\\\\')) {
1507+
function pathToFileURL(filepath, { windows } = {}) {
1508+
if ((windows ?? isWindows) && StringPrototypeStartsWith(filepath, '\\\\')) {
15091509
const outURL = new URL('file://');
15101510
// UNC path format: \\server\share\resource
15111511
const hostnameEndIndex = StringPrototypeIndexOf(filepath, '\\', 2);
@@ -1526,20 +1526,22 @@ function pathToFileURL(filepath) {
15261526
const hostname = StringPrototypeSlice(filepath, 2, hostnameEndIndex);
15271527
outURL.hostname = domainToASCII(hostname);
15281528
outURL.pathname = encodePathChars(
1529-
RegExpPrototypeSymbolReplace(backslashRegEx, StringPrototypeSlice(filepath, hostnameEndIndex), '/'));
1529+
RegExpPrototypeSymbolReplace(backslashRegEx, StringPrototypeSlice(filepath, hostnameEndIndex), '/'),
1530+
{ windows },
1531+
);
15301532
return outURL;
15311533
}
1532-
let resolved = path.resolve(filepath);
1534+
let resolved = (windows ?? isWindows) ? path.win32.resolve(filepath) : path.posix.resolve(filepath);
15331535
// path.resolve strips trailing slashes so we must add them back
15341536
const filePathLast = StringPrototypeCharCodeAt(filepath,
15351537
filepath.length - 1);
15361538
if ((filePathLast === CHAR_FORWARD_SLASH ||
1537-
(isWindows && filePathLast === CHAR_BACKWARD_SLASH)) &&
1539+
((windows ?? isWindows) && filePathLast === CHAR_BACKWARD_SLASH)) &&
15381540
resolved[resolved.length - 1] !== path.sep)
15391541
resolved += '/';
15401542

15411543
// Call encodePathChars first to avoid encoding % again for ? and #.
1542-
resolved = encodePathChars(resolved);
1544+
resolved = encodePathChars(resolved, { windows });
15431545

15441546
// Question and hash character should be included in pathname.
15451547
// Therefore, encoding is required to eliminate parsing them in different states.

test/parallel/test-url-fileurltopath.js

Lines changed: 111 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -49,106 +49,116 @@ assert.throws(() => url.fileURLToPath('https://a/b/c'), {
4949
}
5050
}
5151

52-
{
53-
let testCases;
54-
if (isWindows) {
55-
testCases = [
56-
// Lowercase ascii alpha
57-
{ path: 'C:\\foo', fileURL: 'file:///C:/foo' },
58-
// Uppercase ascii alpha
59-
{ path: 'C:\\FOO', fileURL: 'file:///C:/FOO' },
60-
// dir
61-
{ path: 'C:\\dir\\foo', fileURL: 'file:///C:/dir/foo' },
62-
// trailing separator
63-
{ path: 'C:\\dir\\', fileURL: 'file:///C:/dir/' },
64-
// dot
65-
{ path: 'C:\\foo.mjs', fileURL: 'file:///C:/foo.mjs' },
66-
// space
67-
{ path: 'C:\\foo bar', fileURL: 'file:///C:/foo%20bar' },
68-
// question mark
69-
{ path: 'C:\\foo?bar', fileURL: 'file:///C:/foo%3Fbar' },
70-
// number sign
71-
{ path: 'C:\\foo#bar', fileURL: 'file:///C:/foo%23bar' },
72-
// ampersand
73-
{ path: 'C:\\foo&bar', fileURL: 'file:///C:/foo&bar' },
74-
// equals
75-
{ path: 'C:\\foo=bar', fileURL: 'file:///C:/foo=bar' },
76-
// colon
77-
{ path: 'C:\\foo:bar', fileURL: 'file:///C:/foo:bar' },
78-
// semicolon
79-
{ path: 'C:\\foo;bar', fileURL: 'file:///C:/foo;bar' },
80-
// percent
81-
{ path: 'C:\\foo%bar', fileURL: 'file:///C:/foo%25bar' },
82-
// backslash
83-
{ path: 'C:\\foo\\bar', fileURL: 'file:///C:/foo/bar' },
84-
// backspace
85-
{ path: 'C:\\foo\bbar', fileURL: 'file:///C:/foo%08bar' },
86-
// tab
87-
{ path: 'C:\\foo\tbar', fileURL: 'file:///C:/foo%09bar' },
88-
// newline
89-
{ path: 'C:\\foo\nbar', fileURL: 'file:///C:/foo%0Abar' },
90-
// carriage return
91-
{ path: 'C:\\foo\rbar', fileURL: 'file:///C:/foo%0Dbar' },
92-
// latin1
93-
{ path: 'C:\\fóóbàr', fileURL: 'file:///C:/f%C3%B3%C3%B3b%C3%A0r' },
94-
// Euro sign (BMP code point)
95-
{ path: 'C:\\€', fileURL: 'file:///C:/%E2%82%AC' },
96-
// Rocket emoji (non-BMP code point)
97-
{ path: 'C:\\🚀', fileURL: 'file:///C:/%F0%9F%9A%80' },
98-
// UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows)
99-
{ path: '\\\\nas\\My Docs\\File.doc', fileURL: 'file://nas/My%20Docs/File.doc' },
100-
];
101-
} else {
102-
testCases = [
103-
// Lowercase ascii alpha
104-
{ path: '/foo', fileURL: 'file:///foo' },
105-
// Uppercase ascii alpha
106-
{ path: '/FOO', fileURL: 'file:///FOO' },
107-
// dir
108-
{ path: '/dir/foo', fileURL: 'file:///dir/foo' },
109-
// trailing separator
110-
{ path: '/dir/', fileURL: 'file:///dir/' },
111-
// dot
112-
{ path: '/foo.mjs', fileURL: 'file:///foo.mjs' },
113-
// space
114-
{ path: '/foo bar', fileURL: 'file:///foo%20bar' },
115-
// question mark
116-
{ path: '/foo?bar', fileURL: 'file:///foo%3Fbar' },
117-
// number sign
118-
{ path: '/foo#bar', fileURL: 'file:///foo%23bar' },
119-
// ampersand
120-
{ path: '/foo&bar', fileURL: 'file:///foo&bar' },
121-
// equals
122-
{ path: '/foo=bar', fileURL: 'file:///foo=bar' },
123-
// colon
124-
{ path: '/foo:bar', fileURL: 'file:///foo:bar' },
125-
// semicolon
126-
{ path: '/foo;bar', fileURL: 'file:///foo;bar' },
127-
// percent
128-
{ path: '/foo%bar', fileURL: 'file:///foo%25bar' },
129-
// backslash
130-
{ path: '/foo\\bar', fileURL: 'file:///foo%5Cbar' },
131-
// backspace
132-
{ path: '/foo\bbar', fileURL: 'file:///foo%08bar' },
133-
// tab
134-
{ path: '/foo\tbar', fileURL: 'file:///foo%09bar' },
135-
// newline
136-
{ path: '/foo\nbar', fileURL: 'file:///foo%0Abar' },
137-
// carriage return
138-
{ path: '/foo\rbar', fileURL: 'file:///foo%0Dbar' },
139-
// latin1
140-
{ path: '/fóóbàr', fileURL: 'file:///f%C3%B3%C3%B3b%C3%A0r' },
141-
// Euro sign (BMP code point)
142-
{ path: '/€', fileURL: 'file:///%E2%82%AC' },
143-
// Rocket emoji (non-BMP code point)
144-
{ path: '/🚀', fileURL: 'file:///%F0%9F%9A%80' },
145-
];
146-
}
52+
const windowsTestCases = [
53+
// Lowercase ascii alpha
54+
{ path: 'C:\\foo', fileURL: 'file:///C:/foo' },
55+
// Uppercase ascii alpha
56+
{ path: 'C:\\FOO', fileURL: 'file:///C:/FOO' },
57+
// dir
58+
{ path: 'C:\\dir\\foo', fileURL: 'file:///C:/dir/foo' },
59+
// trailing separator
60+
{ path: 'C:\\dir\\', fileURL: 'file:///C:/dir/' },
61+
// dot
62+
{ path: 'C:\\foo.mjs', fileURL: 'file:///C:/foo.mjs' },
63+
// space
64+
{ path: 'C:\\foo bar', fileURL: 'file:///C:/foo%20bar' },
65+
// question mark
66+
{ path: 'C:\\foo?bar', fileURL: 'file:///C:/foo%3Fbar' },
67+
// number sign
68+
{ path: 'C:\\foo#bar', fileURL: 'file:///C:/foo%23bar' },
69+
// ampersand
70+
{ path: 'C:\\foo&bar', fileURL: 'file:///C:/foo&bar' },
71+
// equals
72+
{ path: 'C:\\foo=bar', fileURL: 'file:///C:/foo=bar' },
73+
// colon
74+
{ path: 'C:\\foo:bar', fileURL: 'file:///C:/foo:bar' },
75+
// semicolon
76+
{ path: 'C:\\foo;bar', fileURL: 'file:///C:/foo;bar' },
77+
// percent
78+
{ path: 'C:\\foo%bar', fileURL: 'file:///C:/foo%25bar' },
79+
// backslash
80+
{ path: 'C:\\foo\\bar', fileURL: 'file:///C:/foo/bar' },
81+
// backspace
82+
{ path: 'C:\\foo\bbar', fileURL: 'file:///C:/foo%08bar' },
83+
// tab
84+
{ path: 'C:\\foo\tbar', fileURL: 'file:///C:/foo%09bar' },
85+
// newline
86+
{ path: 'C:\\foo\nbar', fileURL: 'file:///C:/foo%0Abar' },
87+
// carriage return
88+
{ path: 'C:\\foo\rbar', fileURL: 'file:///C:/foo%0Dbar' },
89+
// latin1
90+
{ path: 'C:\\fóóbàr', fileURL: 'file:///C:/f%C3%B3%C3%B3b%C3%A0r' },
91+
// Euro sign (BMP code point)
92+
{ path: 'C:\\€', fileURL: 'file:///C:/%E2%82%AC' },
93+
// Rocket emoji (non-BMP code point)
94+
{ path: 'C:\\🚀', fileURL: 'file:///C:/%F0%9F%9A%80' },
95+
// UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows)
96+
{ path: '\\\\nas\\My Docs\\File.doc', fileURL: 'file://nas/My%20Docs/File.doc' },
97+
];
98+
const posixTestCases = [
99+
// Lowercase ascii alpha
100+
{ path: '/foo', fileURL: 'file:///foo' },
101+
// Uppercase ascii alpha
102+
{ path: '/FOO', fileURL: 'file:///FOO' },
103+
// dir
104+
{ path: '/dir/foo', fileURL: 'file:///dir/foo' },
105+
// trailing separator
106+
{ path: '/dir/', fileURL: 'file:///dir/' },
107+
// dot
108+
{ path: '/foo.mjs', fileURL: 'file:///foo.mjs' },
109+
// space
110+
{ path: '/foo bar', fileURL: 'file:///foo%20bar' },
111+
// question mark
112+
{ path: '/foo?bar', fileURL: 'file:///foo%3Fbar' },
113+
// number sign
114+
{ path: '/foo#bar', fileURL: 'file:///foo%23bar' },
115+
// ampersand
116+
{ path: '/foo&bar', fileURL: 'file:///foo&bar' },
117+
// equals
118+
{ path: '/foo=bar', fileURL: 'file:///foo=bar' },
119+
// colon
120+
{ path: '/foo:bar', fileURL: 'file:///foo:bar' },
121+
// semicolon
122+
{ path: '/foo;bar', fileURL: 'file:///foo;bar' },
123+
// percent
124+
{ path: '/foo%bar', fileURL: 'file:///foo%25bar' },
125+
// backslash
126+
{ path: '/foo\\bar', fileURL: 'file:///foo%5Cbar' },
127+
// backspace
128+
{ path: '/foo\bbar', fileURL: 'file:///foo%08bar' },
129+
// tab
130+
{ path: '/foo\tbar', fileURL: 'file:///foo%09bar' },
131+
// newline
132+
{ path: '/foo\nbar', fileURL: 'file:///foo%0Abar' },
133+
// carriage return
134+
{ path: '/foo\rbar', fileURL: 'file:///foo%0Dbar' },
135+
// latin1
136+
{ path: '/fóóbàr', fileURL: 'file:///f%C3%B3%C3%B3b%C3%A0r' },
137+
// Euro sign (BMP code point)
138+
{ path: '/€', fileURL: 'file:///%E2%82%AC' },
139+
// Rocket emoji (non-BMP code point)
140+
{ path: '/🚀', fileURL: 'file:///%F0%9F%9A%80' },
141+
];
147142

148-
for (const { path, fileURL } of testCases) {
149-
const fromString = url.fileURLToPath(fileURL);
150-
assert.strictEqual(fromString, path);
151-
const fromURL = url.fileURLToPath(new URL(fileURL));
152-
assert.strictEqual(fromURL, path);
153-
}
143+
for (const { path, fileURL } of windowsTestCases) {
144+
const fromString = url.fileURLToPath(fileURL, { windows: true });
145+
assert.strictEqual(fromString, path);
146+
const fromURL = url.fileURLToPath(new URL(fileURL));
147+
assert.strictEqual(fromURL, path);
148+
}
149+
150+
for (const { path, fileURL } of posixTestCases) {
151+
const fromString = url.fileURLToPath(fileURL, { windows: false });
152+
assert.strictEqual(fromString, path);
153+
const fromURL = url.fileURLToPath(new URL(fileURL));
154+
assert.strictEqual(fromURL, path);
155+
}
156+
157+
const defaultTestCases = isWindows ? windowsTestCases : posixTestCases;
158+
159+
for (const { path, fileURL } of defaultTestCases) {
160+
const fromString = url.fileURLToPath(fileURL);
161+
assert.strictEqual(fromString, path);
162+
const fromURL = url.fileURLToPath(new URL(fileURL));
163+
assert.strictEqual(fromURL, path);
154164
}

0 commit comments

Comments
 (0)