Skip to content

Commit 7b3e38b

Browse files
aduh95injunchoi98
andcommitted
url: handle "unsafe" characters properly in pathToFileURL
Co-authored-by: EarlyRiser42 <tkfydtls464@gmail.com> PR-URL: #54545Fixes: #54515 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent d9494a7 commit 7b3e38b

3 files changed

Lines changed: 67 additions & 42 deletions

File tree

‎lib/internal/process/execution.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ function tryGetCwd() {
4949

5050
letevalIndex=0;
5151
functiongetEvalModuleUrl(){
52-
returnpathToFileURL(`${process.cwd()}/[eval${++evalIndex}]`).href;
52+
return`${pathToFileURL(process.cwd())}/[eval${++evalIndex}]`;
5353
}
5454

5555
/**

‎lib/internal/url.js‎

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,44 +1498,75 @@ function fileURLToPath(path, options = kEmptyObject) {
14981498
return(windows??isWindows) ? getPathFromURLWin32(path) : getPathFromURLPosix(path);
14991499
}
15001500

1501-
// The following characters are percent-encoded when converting from file path
1502-
// to URL:
1503-
// - %: The percent character is the only character not encoded by the
1504-
// `pathname` setter.
1505-
// - \: Backslash is encoded on non-windows platforms since it's a valid
1506-
// character but the `pathname` setters replaces it by a forward slash.
1507-
// - LF: The newline character is stripped out by the `pathname` setter.
1508-
// (See whatwg/url#419)
1509-
// - CR: The carriage return character is also stripped out by the `pathname`
1510-
// setter.
1511-
// - TAB: The tab character is also stripped out by the `pathname` setter.
1501+
// RFC1738 defines the following chars as "unsafe" for URLs
1502+
// @see https://www.ietf.org/rfc/rfc1738.txt 2.2. URL Character Encoding Issues
15121503
constpercentRegEx=/%/g;
1513-
constbackslashRegEx=/\\/g;
15141504
constnewlineRegEx=/\n/g;
15151505
constcarriageReturnRegEx=/\r/g;
15161506
consttabRegEx=/\t/g;
1517-
constquestionRegex=/\?/g;
1507+
constquoteRegEx=/"/g;
15181508
consthashRegex=/#/g;
1509+
constspaceRegEx=//g;
1510+
constquestionMarkRegex=/\?/g;
1511+
constopenSquareBracketRegEx=/\[/g;
1512+
constbackslashRegEx=/\\/g;
1513+
constcloseSquareBracketRegEx=/]/g;
1514+
constcaretRegEx=/\^/g;
1515+
constverticalBarRegEx=/\|/g;
1516+
consttildeRegEx=/~/g;
15191517

15201518
functionencodePathChars(filepath,options=kEmptyObject){
1521-
constwindows=options?.windows;
1522-
if(StringPrototypeIndexOf(filepath,'%')!==-1)
1519+
if(StringPrototypeIncludes(filepath,'%')){
15231520
filepath=RegExpPrototypeSymbolReplace(percentRegEx,filepath,'%25');
1524-
// In posix, backslash is a valid character in paths:
1525-
if(!(windows??isWindows)&&StringPrototypeIndexOf(filepath,'\\')!==-1)
1526-
filepath=RegExpPrototypeSymbolReplace(backslashRegEx,filepath,'%5C');
1527-
if(StringPrototypeIndexOf(filepath,'\n')!==-1)
1521+
}
1522+
1523+
if(StringPrototypeIncludes(filepath,'\t')){
1524+
filepath=RegExpPrototypeSymbolReplace(tabRegEx,filepath,'%09');
1525+
}
1526+
if(StringPrototypeIncludes(filepath,'\n')){
15281527
filepath=RegExpPrototypeSymbolReplace(newlineRegEx,filepath,'%0A');
1529-
if(StringPrototypeIndexOf(filepath,'\r')!==-1)
1528+
}
1529+
if(StringPrototypeIncludes(filepath,'\r')){
15301530
filepath=RegExpPrototypeSymbolReplace(carriageReturnRegEx,filepath,'%0D');
1531-
if(StringPrototypeIndexOf(filepath,'\t')!==-1)
1532-
filepath=RegExpPrototypeSymbolReplace(tabRegEx,filepath,'%09');
1531+
}
1532+
if(StringPrototypeIncludes(filepath,' ')){
1533+
filepath=RegExpPrototypeSymbolReplace(spaceRegEx,filepath,'%20');
1534+
}
1535+
if(StringPrototypeIncludes(filepath,'"')){
1536+
filepath=RegExpPrototypeSymbolReplace(quoteRegEx,filepath,'%22');
1537+
}
1538+
if(StringPrototypeIncludes(filepath,'#')){
1539+
filepath=RegExpPrototypeSymbolReplace(hashRegex,filepath,'%23');
1540+
}
1541+
if(StringPrototypeIncludes(filepath,'?')){
1542+
filepath=RegExpPrototypeSymbolReplace(questionMarkRegex,filepath,'%3F');
1543+
}
1544+
if(StringPrototypeIncludes(filepath,'[')){
1545+
filepath=RegExpPrototypeSymbolReplace(openSquareBracketRegEx,filepath,'%5B');
1546+
}
1547+
// Back-slashes must be special-cased on Windows, where they are treated as path separator.
1548+
if(!options.windows&&StringPrototypeIncludes(filepath,'\\')){
1549+
filepath=RegExpPrototypeSymbolReplace(backslashRegEx,filepath,'%5C');
1550+
}
1551+
if(StringPrototypeIncludes(filepath,']')){
1552+
filepath=RegExpPrototypeSymbolReplace(closeSquareBracketRegEx,filepath,'%5D');
1553+
}
1554+
if(StringPrototypeIncludes(filepath,'^')){
1555+
filepath=RegExpPrototypeSymbolReplace(caretRegEx,filepath,'%5E');
1556+
}
1557+
if(StringPrototypeIncludes(filepath,'|')){
1558+
filepath=RegExpPrototypeSymbolReplace(verticalBarRegEx,filepath,'%7C');
1559+
}
1560+
if(StringPrototypeIncludes(filepath,'~')){
1561+
filepath=RegExpPrototypeSymbolReplace(tildeRegEx,filepath,'%7E');
1562+
}
1563+
15331564
returnfilepath;
15341565
}
15351566

15361567
functionpathToFileURL(filepath,options=kEmptyObject){
1537-
constwindows=options?.windows;
1538-
if((windows??isWindows)&&StringPrototypeStartsWith(filepath,'\\\\')){
1568+
constwindows=options?.windows??isWindows;
1569+
if(windows&&StringPrototypeStartsWith(filepath,'\\\\')){
15391570
constoutURL=newURL('file://');
15401571
// UNC path format: \\server\share\resource
15411572
// Handle extended UNC path and standard UNC path
@@ -1566,20 +1597,9 @@ function pathToFileURL(filepath, options = kEmptyObject) {
15661597
);
15671598
returnoutURL;
15681599
}
1569-
letresolved=(windows??isWindows) ? path.win32.resolve(filepath) : path.posix.resolve(filepath);
1570-
1571-
// Call encodePathChars first to avoid encoding % again for ? and #.
1572-
resolved=encodePathChars(resolved,{ windows });
1600+
constresolved=windows ? path.win32.resolve(filepath) : path.posix.resolve(filepath);
15731601

1574-
// Question and hash character should be included in pathname.
1575-
// Therefore, encoding is required to eliminate parsing them in different states.
1576-
// This is done as an optimization to not creating a URL instance and
1577-
// later triggering pathname setter, which impacts performance
1578-
if(StringPrototypeIndexOf(resolved,'?')!==-1)
1579-
resolved=RegExpPrototypeSymbolReplace(questionRegex,resolved,'%3F');
1580-
if(StringPrototypeIndexOf(resolved,'#')!==-1)
1581-
resolved=RegExpPrototypeSymbolReplace(hashRegex,resolved,'%23');
1582-
returnnewURL(`file://${resolved}`);
1602+
returnnewURL(`file://${encodePathChars(resolved,{ windows })}`);
15831603
}
15841604

15851605
functiontoPathIfFileURL(fileURLOrPath){

‎test/parallel/test-url-pathtofileurl.js‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ const url = require('url');
1313
{
1414
constfileURL=url.pathToFileURL('test\\').href;
1515
assert.ok(fileURL.startsWith('file:///'));
16-
if(isWindows)
17-
assert.ok(fileURL.endsWith('/'));
18-
else
19-
assert.ok(fileURL.endsWith('%5C'));
16+
assert.match(fileURL,isWindows ? /\/$/ : /%5C$/);
2017
}
2118

2219
{
@@ -104,6 +101,12 @@ const windowsTestCases = [
104101
{path: 'C:\\€',expected: 'file:///C:/%E2%82%AC'},
105102
// Rocket emoji (non-BMP code point)
106103
{path: 'C:\\🚀',expected: 'file:///C:/%F0%9F%9A%80'},
104+
// caret
105+
{path: 'C:\\foo^bar',expected: 'file:///C:/foo%5Ebar'},
106+
// left bracket
107+
{path: 'C:\\foo[bar',expected: 'file:///C:/foo%5Bbar'},
108+
// right bracket
109+
{path: 'C:\\foo]bar',expected: 'file:///C:/foo%5Dbar'},
107110
// Local extended path
108111
{path: '\\\\?\\C:\\path\\to\\file.txt',expected: 'file:///C:/path/to/file.txt'},
109112
// UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows)
@@ -154,6 +157,8 @@ const posixTestCases = [
154157
{path: '/€',expected: 'file:///%E2%82%AC'},
155158
// Rocket emoji (non-BMP code point)
156159
{path: '/🚀',expected: 'file:///%F0%9F%9A%80'},
160+
// "unsafe" chars
161+
{path: '/foo\r\n\t<>"#%{}|^[\\~]`?bar',expected: 'file:///foo%0D%0A%09%3C%3E%22%23%25%7B%7D%7C%5E%5B%5C%7E%5D%60%3Fbar'},
157162
];
158163

159164
for(const{ path, expected }ofwindowsTestCases){

0 commit comments

Comments
 (0)