Skip to content

Commit d881b33

Browse files
demurgosBridgeAR
authored andcommitted
url: support LF, CR and TAB in pathToFileURL
Fixes: #23696 PR-URL: #23720 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
1 parent 5a1fb1e commit d881b33

File tree

2 files changed

+120
-3
lines changed

2 files changed

+120
-3
lines changed

lib/internal/url.js

+20-3
Original file line numberDiff line numberDiff line change
@@ -1339,11 +1339,22 @@ function fileURLToPath(path) {
13391339
return isWindows ? getPathFromURLWin32(path) : getPathFromURLPosix(path);
13401340
}
13411341

1342-
// We percent-encode % character when converting from file path to URL,
1343-
// as this is the only character that won't be percent encoded by
1344-
// default URL percent encoding when pathname is set.
1342+
// The following characters are percent-encoded when converting from file path
1343+
// to URL:
1344+
// - %: The percent character is the only character not encoded by the
1345+
// `pathname` setter.
1346+
// - \: Backslash is encoded on non-windows platforms since it's a valid
1347+
// character but the `pathname` setters replaces it by a forward slash.
1348+
// - LF: The newline character is stripped out by the `pathname` setter.
1349+
// (See whatwg/url#419)
1350+
// - CR: The carriage return character is also stripped out by the `pathname`
1351+
// setter.
1352+
// - TAB: The tab character is also stripped out by the `pathname` setter.
13451353
const percentRegEx = /%/g;
13461354
const backslashRegEx = /\\/g;
1355+
const newlineRegEx = /\n/g;
1356+
const carriageReturnRegEx = /\r/g;
1357+
const tabRegEx = /\t/g;
13471358
function pathToFileURL(filepath) {
13481359
let resolved = path.resolve(filepath);
13491360
// path.resolve strips trailing slashes so we must add them back
@@ -1358,6 +1369,12 @@ function pathToFileURL(filepath) {
13581369
// in posix, "/" is a valid character in paths
13591370
if (!isWindows && resolved.includes('\\'))
13601371
resolved = resolved.replace(backslashRegEx, '%5C');
1372+
if (resolved.includes('\n'))
1373+
resolved = resolved.replace(newlineRegEx, '%0A');
1374+
if (resolved.includes('\r'))
1375+
resolved = resolved.replace(carriageReturnRegEx, '%0D');
1376+
if (resolved.includes('\t'))
1377+
resolved = resolved.replace(tabRegEx, '%09');
13611378
outURL.pathname = resolved;
13621379
return outURL;
13631380
}

test/parallel/test-url-pathtofileurl.js

+100
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,103 @@ const url = require('url');
2222
const fileURL = url.pathToFileURL('test/%').href;
2323
assert.ok(fileURL.includes('%25'));
2424
}
25+
26+
{
27+
let testCases;
28+
if (isWindows) {
29+
testCases = [
30+
// lowercase ascii alpha
31+
{ path: 'C:\\foo', expected: 'file:///C:/foo' },
32+
// uppercase ascii alpha
33+
{ path: 'C:\\FOO', expected: 'file:///C:/FOO' },
34+
// dir
35+
{ path: 'C:\\dir\\foo', expected: 'file:///C:/dir/foo' },
36+
// trailing separator
37+
{ path: 'C:\\dir\\', expected: 'file:///C:/dir/' },
38+
// dot
39+
{ path: 'C:\\foo.mjs', expected: 'file:///C:/foo.mjs' },
40+
// space
41+
{ path: 'C:\\foo bar', expected: 'file:///C:/foo%20bar' },
42+
// question mark
43+
{ path: 'C:\\foo?bar', expected: 'file:///C:/foo%3Fbar' },
44+
// number sign
45+
{ path: 'C:\\foo#bar', expected: 'file:///C:/foo%23bar' },
46+
// ampersand
47+
{ path: 'C:\\foo&bar', expected: 'file:///C:/foo&bar' },
48+
// equals
49+
{ path: 'C:\\foo=bar', expected: 'file:///C:/foo=bar' },
50+
// colon
51+
{ path: 'C:\\foo:bar', expected: 'file:///C:/foo:bar' },
52+
// semicolon
53+
{ path: 'C:\\foo;bar', expected: 'file:///C:/foo;bar' },
54+
// percent
55+
{ path: 'C:\\foo%bar', expected: 'file:///C:/foo%25bar' },
56+
// backslash
57+
{ path: 'C:\\foo\\bar', expected: 'file:///C:/foo/bar' },
58+
// backspace
59+
{ path: 'C:\\foo\bbar', expected: 'file:///C:/foo%08bar' },
60+
// tab
61+
{ path: 'C:\\foo\tbar', expected: 'file:///C:/foo%09bar' },
62+
// newline
63+
{ path: 'C:\\foo\nbar', expected: 'file:///C:/foo%0Abar' },
64+
// carriage return
65+
{ path: 'C:\\foo\rbar', expected: 'file:///C:/foo%0Dbar' },
66+
// latin1
67+
{ path: 'C:\\fóóbàr', expected: 'file:///C:/f%C3%B3%C3%B3b%C3%A0r' },
68+
// euro sign (BMP code point)
69+
{ path: 'C:\\€', expected: 'file:///C:/%E2%82%AC' },
70+
// rocket emoji (non-BMP code point)
71+
{ path: 'C:\\🚀', expected: 'file:///C:/%F0%9F%9A%80' }
72+
];
73+
} else {
74+
testCases = [
75+
// lowercase ascii alpha
76+
{ path: '/foo', expected: 'file:///foo' },
77+
// uppercase ascii alpha
78+
{ path: '/FOO', expected: 'file:///FOO' },
79+
// dir
80+
{ path: '/dir/foo', expected: 'file:///dir/foo' },
81+
// trailing separator
82+
{ path: '/dir/', expected: 'file:///dir/' },
83+
// dot
84+
{ path: '/foo.mjs', expected: 'file:///foo.mjs' },
85+
// space
86+
{ path: '/foo bar', expected: 'file:///foo%20bar' },
87+
// question mark
88+
{ path: '/foo?bar', expected: 'file:///foo%3Fbar' },
89+
// number sign
90+
{ path: '/foo#bar', expected: 'file:///foo%23bar' },
91+
// ampersand
92+
{ path: '/foo&bar', expected: 'file:///foo&bar' },
93+
// equals
94+
{ path: '/foo=bar', expected: 'file:///foo=bar' },
95+
// colon
96+
{ path: '/foo:bar', expected: 'file:///foo:bar' },
97+
// semicolon
98+
{ path: '/foo;bar', expected: 'file:///foo;bar' },
99+
// percent
100+
{ path: '/foo%bar', expected: 'file:///foo%25bar' },
101+
// backslash
102+
{ path: '/foo\\bar', expected: 'file:///foo%5Cbar' },
103+
// backspace
104+
{ path: '/foo\bbar', expected: 'file:///foo%08bar' },
105+
// tab
106+
{ path: '/foo\tbar', expected: 'file:///foo%09bar' },
107+
// newline
108+
{ path: '/foo\nbar', expected: 'file:///foo%0Abar' },
109+
// carriage return
110+
{ path: '/foo\rbar', expected: 'file:///foo%0Dbar' },
111+
// latin1
112+
{ path: '/fóóbàr', expected: 'file:///f%C3%B3%C3%B3b%C3%A0r' },
113+
// euro sign (BMP code point)
114+
{ path: '/€', expected: 'file:///%E2%82%AC' },
115+
// rocket emoji (non-BMP code point)
116+
{ path: '/🚀', expected: 'file:///%F0%9F%9A%80' },
117+
];
118+
}
119+
120+
for (const { path, expected } of testCases) {
121+
const actual = url.pathToFileURL(path).href;
122+
assert.strictEqual(actual, expected);
123+
}
124+
}

0 commit comments

Comments
 (0)