@@ -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
15121503const percentRegEx = / % / g;
1513- const backslashRegEx = / \\ / g;
15141504const newlineRegEx = / \n / g;
15151505const carriageReturnRegEx = / \r / g;
15161506const tabRegEx = / \t / g;
1517- const questionRegex = / \? / g;
1507+ const quoteRegEx = / " / g;
15181508const hashRegex = / # / g;
1509+ const spaceRegEx = / / g;
1510+ const questionMarkRegex = / \? / g;
1511+ const openSquareBracketRegEx = / \[ / g;
1512+ const backslashRegEx = / \\ / g;
1513+ const closeSquareBracketRegEx = / ] / g;
1514+ const caretRegEx = / \^ / g;
1515+ const verticalBarRegEx = / \| / g;
1516+ const tildeRegEx = / ~ / g;
15191517
15201518function encodePathChars ( filepath , options = kEmptyObject ) {
1521- const windows = 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 return filepath ;
15341565}
15351566
15361567function pathToFileURL ( filepath , options = kEmptyObject ) {
1537- const windows = options ?. windows ;
1538- if ( ( windows ?? isWindows ) && StringPrototypeStartsWith ( filepath , '\\\\' ) ) {
1568+ const windows = options ?. windows ?? isWindows ;
1569+ if ( windows && StringPrototypeStartsWith ( filepath , '\\\\' ) ) {
15391570 const outURL = new URL ( '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 return outURL ;
15681599 }
1569- let resolved = ( 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+ const resolved = 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- return new URL ( `file://${ resolved } ` ) ;
1602+ return new URL ( `file://${ encodePathChars ( resolved , { windows } ) } ` ) ;
15831603}
15841604
15851605function toPathIfFileURL ( fileURLOrPath ) {
0 commit comments