Skip to content

Commit 5418d40

Browse files
aduh95injunchoi98
authored 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 ee17fcd commit 5418d40

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
@@ -1500,44 +1500,75 @@ function fileURLToPath(path, options = kEmptyObject) {
15001500
return(windows??isWindows) ? getPathFromURLWin32(path) : getPathFromURLPosix(path);
15011501
}
15021502

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

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

15381569
functionpathToFileURL(filepath,options=kEmptyObject){
1539-
constwindows=options?.windows;
1540-
if((windows??isWindows)&&StringPrototypeStartsWith(filepath,'\\\\')){
1570+
constwindows=options?.windows??isWindows;
1571+
if(windows&&StringPrototypeStartsWith(filepath,'\\\\')){
15411572
constoutURL=newURL('file://');
15421573
// UNC path format: \\server\share\resource
15431574
// Handle extended UNC path and standard UNC path
@@ -1568,7 +1599,7 @@ function pathToFileURL(filepath, options = kEmptyObject) {
15681599
);
15691600
returnoutURL;
15701601
}
1571-
letresolved=(windows??isWindows) ? path.win32.resolve(filepath) : path.posix.resolve(filepath);
1602+
letresolved=windows ? path.win32.resolve(filepath) : path.posix.resolve(filepath);
15721603
// path.resolve strips trailing slashes so we must add them back
15731604
constfilePathLast=StringPrototypeCharCodeAt(filepath,
15741605
filepath.length-1);
@@ -1577,18 +1608,7 @@ function pathToFileURL(filepath, options = kEmptyObject) {
15771608
resolved[resolved.length-1]!==path.sep)
15781609
resolved+='/';
15791610

1580-
// Call encodePathChars first to avoid encoding % again for ? and #.
1581-
resolved=encodePathChars(resolved,{ windows });
1582-
1583-
// Question and hash character should be included in pathname.
1584-
// Therefore, encoding is required to eliminate parsing them in different states.
1585-
// This is done as an optimization to not creating a URL instance and
1586-
// later triggering pathname setter, which impacts performance
1587-
if(StringPrototypeIndexOf(resolved,'?')!==-1)
1588-
resolved=RegExpPrototypeSymbolReplace(questionRegex,resolved,'%3F');
1589-
if(StringPrototypeIndexOf(resolved,'#')!==-1)
1590-
resolved=RegExpPrototypeSymbolReplace(hashRegex,resolved,'%23');
1591-
returnnewURL(`file://${resolved}`);
1611+
returnnewURL(`file://${encodePathChars(resolved,{ windows })}`);
15921612
}
15931613

15941614
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)