Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 075cfbf

Browse filesBrowse files
guybedfordtargos
authored andcommitted
module: resolver & spec hardening /w refactoring
PR-URL: #40510 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 8a00dc5 commit 075cfbf
Copy full SHA for 075cfbf

File tree

Expand file treeCollapse file tree

3 files changed

+88
-89
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

3 files changed

+88
-89
lines changed
Open diff view settings
Collapse file

‎doc/api/esm.md‎

Copy file name to clipboardExpand all lines: doc/api/esm.md
+52-44Lines changed: 52 additions & 44 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1076,59 +1076,63 @@ The resolver can throw the following errors:
10761076
> 1. Note: _specifier_ is now a bare specifier.
10771077
> 2. Set _resolved_ the result of
10781078
> **PACKAGE\_RESOLVE**(_specifier_, _parentURL_).
1079-
> 6. If _resolved_ contains any percent encodings of _"/"_ or _"\\"_ (_"%2f"_
1080-
> and _"%5C"_ respectively), then
1081-
> 1. Throw an _Invalid Module Specifier_ error.
1082-
> 7. If the file at _resolved_ is a directory, then
1083-
> 1. Throw an _Unsupported Directory Import_ error.
1084-
> 8. If the file at _resolved_ does not exist, then
1085-
> 1. Throw a _Module Not Found_ error.
1086-
> 9. Set _resolved_ to the real path of _resolved_.
1087-
> 10. Let _format_ be the result of **ESM\_FORMAT**(_resolved_).
1088-
> 11. Load _resolved_ as module format, _format_.
1089-
> 12. Return _resolved_.
1079+
> 6. Let _format_ be **undefined**.
1080+
> 7. If _resolved_ is a _"file:"_ URL, then
1081+
> 1. If _resolved_ contains any percent encodings of _"/"_ or _"\\"_ (_"%2F"_
1082+
> and _"%5C"_ respectively), then
1083+
> 1. Throw an _Invalid Module Specifier_ error.
1084+
> 2. If the file at _resolved_ is a directory, then
1085+
> 1. Throw an _Unsupported Directory Import_ error.
1086+
> 3. If the file at _resolved_ does not exist, then
1087+
> 1. Throw a _Module Not Found_ error.
1088+
> 4. Set _resolved_ to the real path of _resolved_, maintaining the
1089+
> same URL querystring and fragment components.
1090+
> 5. Set _format_ to the result of **ESM\_FILE\_FORMAT**(_resolved_).
1091+
> 8. Otherwise,
1092+
> 1. Set _format_ the module format of the content type associated with the
1093+
> URL _resolved_.
1094+
> 9. Load _resolved_ as module format, _format_.
10901095

10911096
**PACKAGE\_RESOLVE**(_packageSpecifier_, _parentURL_)
10921097

10931098
> 1. Let _packageName_ be **undefined**.
10941099
> 2. If _packageSpecifier_ is an empty string, then
10951100
> 1. Throw an _Invalid Module Specifier_ error.
1096-
> 3. If _packageSpecifier_ does not start with _"@"_, then
1101+
> 3. If _packageSpecifier_ is a Node.js builtin module name, then
1102+
> 1. Return the string _"node:"_ concatenated with _packageSpecifier_.
1103+
> 4. If _packageSpecifier_ does not start with _"@"_, then
10971104
> 1. Set _packageName_ to the substring of _packageSpecifier_ until the first
10981105
> _"/"_ separator or the end of the string.
1099-
> 4. Otherwise,
1106+
> 5. Otherwise,
11001107
> 1. If _packageSpecifier_ does not contain a _"/"_ separator, then
11011108
> 1. Throw an _Invalid Module Specifier_ error.
11021109
> 2. Set _packageName_ to the substring of _packageSpecifier_
11031110
> until the second _"/"_ separator or the end of the string.
1104-
> 5. If _packageName_ starts with _"."_ or contains _"\\"_ or _"%"_, then
1111+
> 6. If _packageName_ starts with _"."_ or contains _"\\"_ or _"%"_, then
11051112
> 1. Throw an _Invalid Module Specifier_ error.
1106-
> 6. Let _packageSubpath_ be _"."_ concatenated with the substring of
1113+
> 7. Let _packageSubpath_ be _"."_ concatenated with the substring of
11071114
> _packageSpecifier_ from the position at the length of _packageName_.
1108-
> 7. If _packageSubpath_ ends in _"/"_, then
1115+
> 8. If _packageSubpath_ ends in _"/"_, then
11091116
> 1. Throw an _Invalid Module Specifier_ error.
1110-
> 8. Let _selfUrl_ be the result of
1117+
> 9. Let _selfUrl_ be the result of
11111118
> **PACKAGE\_SELF\_RESOLVE**(_packageName_, _packageSubpath_, _parentURL_).
1112-
> 9. If _selfUrl_ is not **undefined**, return _selfUrl_.
1113-
> 10. If _packageSubpath_ is _"."_ and _packageName_ is a Node.js builtin
1114-
> module, then
1115-
> 1. Return the string _"node:"_ concatenated with _packageSpecifier_.
1119+
> 10. If _selfUrl_ is not **undefined**, return _selfUrl_.
11161120
> 11. While _parentURL_ is not the file system root,
1117-
> 1. Let _packageURL_ be the URL resolution of _"node\_modules/"_
1118-
> concatenated with _packageSpecifier_, relative to _parentURL_.
1119-
> 2. Set _parentURL_ to the parent folder URL of _parentURL_.
1120-
> 3. If the folder at _packageURL_ does not exist, then
1121-
> 1. Continue the next loop iteration.
1122-
> 4. Let _pjson_ be the result of **READ\_PACKAGE\_JSON**(_packageURL_).
1123-
> 5. If _pjson_ is not **null** and _pjson_._exports_ is not **null** or
1124-
> **undefined**, then
1125-
> 1. Return the result of **PACKAGE\_EXPORTS\_RESOLVE**(_packageURL_,
1126-
> _packageSubpath_, _pjson.exports_, _defaultConditions_).
1127-
> 6. Otherwise, if _packageSubpath_ is equal to _"."_, then
1128-
> 1. If _pjson.main_ is a string, then
1129-
> 1. Return the URL resolution of _main_ in _packageURL_.
1130-
> 7. Otherwise,
1131-
> 1. Return the URL resolution of _packageSubpath_ in _packageURL_.
1121+
> 1. Let _packageURL_ be the URL resolution of _"node_modules/"_
1122+
> concatenated with _packageSpecifier_, relative to _parentURL_.
1123+
> 2. Set _parentURL_ to the parent folder URL of _parentURL_.
1124+
> 3. If the folder at _packageURL_ does not exist, then
1125+
> 1. Continue the next loop iteration.
1126+
> 4. Let _pjson_ be the result of **READ\_PACKAGE\_JSON**(_packageURL_).
1127+
> 5. If _pjson_ is not **null** and _pjson_._exports_ is not **null** or
1128+
> **undefined**, then
1129+
> 1. Return the result of **PACKAGE\_EXPORTS\_RESOLVE**(_packageURL_,
1130+
> _packageSubpath_, _pjson.exports_, _defaultConditions_).
1131+
> 6. Otherwise, if _packageSubpath_ is equal to _"."_, then
1132+
> 1. If _pjson.main_ is a string, then
1133+
> 1. Return the URL resolution of _main_ in _packageURL_.
1134+
> 7. Otherwise,
1135+
> 1. Return the URL resolution of _packageSubpath_ in _packageURL_.
11321136
> 12. Throw a _Module Not Found_ error.
11331137

11341138
**PACKAGE\_SELF\_RESOLVE**(_packageName_, _packageSubpath_, _parentURL_)
@@ -1238,18 +1242,20 @@ _internal_, _conditions_)
12381242
> _"/"_ and is not a valid URL, then
12391243
> 1. If _pattern_ is **true**, then
12401244
> 1. Return **PACKAGE\_RESOLVE**(_target_ with every instance of
1241-
> _"\*"_ replaced by _subpath_, _packageURL_ + _"/"_)\_.
1245+
> _"\*"_ replaced by _subpath_, _packageURL_ + _"/"_).
12421246
> 2. Return **PACKAGE\_RESOLVE**(_target_ + _subpath_,
1243-
> _packageURL_ + _"/"_)\_.
1247+
> _packageURL_ + _"/"_)_.
12441248
> 2. Otherwise, throw an _Invalid Package Target_ error.
12451249
> 3. If _target_ split on _"/"_ or _"\\"_ contains any _"."_, _".."_ or
1246-
> _"node\_modules"_ segments after the first segment, throw an
1247-
> _Invalid Package Target_ error.
1250+
> _"node\_modules"_ segments after the first segment, case insensitive and
1251+
> including percent encoded variants, throw an _Invalid Package Target_
1252+
> error.
12481253
> 4. Let _resolvedTarget_ be the URL resolution of the concatenation of
12491254
> _packageURL_ and _target_.
12501255
> 5. Assert: _resolvedTarget_ is contained in _packageURL_.
12511256
> 6. If _subpath_ split on _"/"_ or _"\\"_ contains any _"."_, _".."_ or
1252-
> _"node\_modules"_ segments, throw an _Invalid Module Specifier_ error.
1257+
> _"node\_modules"_ segments, case insensitive and including percent
1258+
> encoded variants, throw an _Invalid Module Specifier_ error.
12531259
> 7. If _pattern_ is **true**, then
12541260
> 1. Return the URL resolution of _resolvedTarget_ with every instance of
12551261
> _"\*"_ replaced with _subpath_.
@@ -1282,19 +1288,21 @@ _internal_, _conditions_)
12821288
> 4. Otherwise, if _target_ is _null_, return **null**.
12831289
> 5. Otherwise throw an _Invalid Package Target_ error.
12841290

1285-
**ESM\_FORMAT**(_url_)
1291+
**ESM\_FILE\_FORMAT**(_url_)
12861292

12871293
> 1. Assert: _url_ corresponds to an existing file.
12881294
> 2. Let _pjson_ be the result of **READ\_PACKAGE\_SCOPE**(_url_).
12891295
> 3. If _url_ ends in _".mjs"_, then
12901296
> 1. Return _"module"_.
12911297
> 4. If _url_ ends in _".cjs"_, then
12921298
> 1. Return _"commonjs"_.
1293-
> 5. If _pjson?.type_ exists and is _"module"_, then
1299+
> 5. If _url_ ends in _".json"_, then
1300+
> 1. Return _"json"_.
1301+
> 6. If _pjson?.type_ exists and is _"module"_, then
12941302
> 1. If _url_ ends in _".js"_, then
12951303
> 1. Return _"module"_.
12961304
> 2. Throw an _Unsupported File Extension_ error.
1297-
> 6. Otherwise,
1305+
> 7. Otherwise,
12981306
> 1. Throw an _Unsupported File Extension_ error.
12991307

13001308
**READ\_PACKAGE\_SCOPE**(_url_)
Collapse file

‎lib/internal/modules/esm/resolve.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/esm/resolve.js
+32-45Lines changed: 32 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const {
1010
ObjectGetOwnPropertyNames,
1111
ObjectPrototypeHasOwnProperty,
1212
RegExp,
13+
RegExpPrototypeExec,
1314
RegExpPrototypeSymbolReplace,
1415
RegExpPrototypeTest,
1516
SafeMap,
@@ -360,9 +361,10 @@ const encodedSepRegEx = /%2F|%5C/i;
360361
/**
361362
* @param {URL} resolved
362363
* @param {string | URL | undefined} base
364+
* @param {boolean} preserveSymlinks
363365
* @returns {URL | undefined}
364366
*/
365-
function finalizeResolution(resolved, base) {
367+
function finalizeResolution(resolved, base, preserveSymlinks) {
366368
if (RegExpPrototypeTest(encodedSepRegEx, resolved.pathname))
367369
throw new ERR_INVALID_MODULE_SPECIFIER(
368370
resolved.pathname, 'must not include encoded "/" or "\\" characters',
@@ -393,6 +395,17 @@ function finalizeResolution(resolved, base) {
393395
path || resolved.pathname, base && fileURLToPath(base), 'module');
394396
}
395397

398+
if (!preserveSymlinks) {
399+
const real = realpathSync(path, {
400+
[internalFS.realpathCacheKey]: realpathCache
401+
});
402+
const { search, hash } = resolved;
403+
resolved =
404+
pathToFileURL(real + (StringPrototypeEndsWith(path, sep) ? '/' : ''));
405+
resolved.search = search;
406+
resolved.hash = hash;
407+
}
408+
396409
return resolved;
397410
}
398411

@@ -444,7 +457,8 @@ function throwInvalidPackageTarget(
444457
internal, base && fileURLToPath(base));
445458
}
446459

447-
const invalidSegmentRegEx = /(^|\\|\/)(\.\.?|node_modules)(\\|\/|$)/;
460+
const invalidSegmentRegEx = /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\|\/|$)/i;
461+
const invalidPackageNameRegEx = /^\.|%|\\/;
448462
const patternRegEx = /\*/g;
449463

450464
function resolvePackageTargetString(
@@ -777,13 +791,9 @@ function parsePackageName(specifier, base) {
777791
specifier : StringPrototypeSlice(specifier, 0, separatorIndex);
778792

779793
// Package name cannot have leading . and cannot have percent-encoding or
780-
// separators.
781-
for (let i = 0; i < packageName.length; i++) {
782-
if (packageName[i] === '%' || packageName[i] === '\\') {
783-
validPackageName = false;
784-
break;
785-
}
786-
}
794+
// \\ separators.
795+
if (RegExpPrototypeExec(invalidPackageNameRegEx, packageName) !== null)
796+
validPackageName = false;
787797

788798
if (!validPackageName) {
789799
throw new ERR_INVALID_MODULE_SPECIFIER(
@@ -803,6 +813,9 @@ function parsePackageName(specifier, base) {
803813
* @returns {URL}
804814
*/
805815
function packageResolve(specifier, base, conditions) {
816+
if (NativeModule.canBeRequiredByUsers(specifier))
817+
return new URL('node:' + specifier);
818+
806819
const { packageName, packageSubpath, isScoped } =
807820
parsePackageName(specifier, base);
808821

@@ -879,9 +892,10 @@ function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) {
879892
* @param {string} specifier
880893
* @param {string | URL | undefined} base
881894
* @param {Set<string>} conditions
895+
* @param {boolean} preserveSymlinks
882896
* @returns {URL}
883897
*/
884-
function moduleResolve(specifier, base, conditions) {
898+
function moduleResolve(specifier, base, conditions, preserveSymlinks) {
885899
// Order swapped from spec for minor perf gain.
886900
// Ok since relative URLs cannot parse as URLs.
887901
let resolved;
@@ -896,7 +910,9 @@ function moduleResolve(specifier, base, conditions) {
896910
resolved = packageResolve(specifier, base, conditions);
897911
}
898912
}
899-
return finalizeResolution(resolved, base);
913+
if (resolved.protocol !== 'file:')
914+
return resolved;
915+
return finalizeResolution(resolved, base, preserveSymlinks);
900916
}
901917

902918
/**
@@ -968,28 +984,6 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
968984
}
969985
}
970986
}
971-
let parsed;
972-
try {
973-
parsed = new URL(specifier);
974-
if (parsed.protocol === 'data:') {
975-
return {
976-
url: specifier
977-
};
978-
}
979-
} catch {}
980-
if (parsed && parsed.protocol === 'node:')
981-
return { url: specifier };
982-
if (parsed && parsed.protocol !== 'file:' && parsed.protocol !== 'data:')
983-
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(parsed);
984-
if (NativeModule.canBeRequiredByUsers(specifier)) {
985-
return {
986-
url: 'node:' + specifier
987-
};
988-
}
989-
if (parentURL && StringPrototypeStartsWith(parentURL, 'data:')) {
990-
// This is gonna blow up, we want the error
991-
new URL(specifier, parentURL);
992-
}
993987

994988
const isMain = parentURL === undefined;
995989
if (isMain) {
@@ -1008,7 +1002,8 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
10081002
conditions = getConditionsSet(conditions);
10091003
let url;
10101004
try {
1011-
url = moduleResolve(specifier, parentURL, conditions);
1005+
url = moduleResolve(specifier, parentURL, conditions,
1006+
isMain ? preserveSymlinksMain : preserveSymlinks);
10121007
} catch (error) {
10131008
// Try to give the user a hint of what would have been the
10141009
// resolved CommonJS module
@@ -1032,17 +1027,9 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
10321027
throw error;
10331028
}
10341029

1035-
if (isMain ? !preserveSymlinksMain : !preserveSymlinks) {
1036-
const urlPath = fileURLToPath(url);
1037-
const real = realpathSync(urlPath, {
1038-
[internalFS.realpathCacheKey]: realpathCache
1039-
});
1040-
const old = url;
1041-
url = pathToFileURL(
1042-
real + (StringPrototypeEndsWith(urlPath, sep) ? '/' : ''));
1043-
url.search = old.search;
1044-
url.hash = old.hash;
1045-
}
1030+
if (url.protocol !== 'file:' && url.protocol !== 'data:' &&
1031+
url.protocol !== 'node:')
1032+
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url);
10461033

10471034
return { url: `${url}` };
10481035
}
Collapse file

‎test/es-module/test-esm-exports.mjs‎

Copy file name to clipboardExpand all lines: test/es-module/test-esm-exports.mjs
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
104104
// Even though 'pkgexports/sub/asdf.js' works, alternate "path-like"
105105
// variants do not to prevent confusion and accidental loopholes.
106106
['pkgexports/sub/./../asdf.js', './sub/./../asdf.js'],
107+
// Cannot reach into node_modules, even percent encoded
108+
['pkgexports/sub/no%64e_modules', './sub/no%64e_modules'],
109+
// Cannot backtrack below exposed path, even with percent encoded "."
110+
['pkgexports/sub/%2e./asdf', './asdf'],
107111
]);
108112

109113
for (const [specifier, subpath] of undefinedExports) {

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.