module: resolver & spec hardening /w refactoring · nodejs/node@e7391ea · GitHub
Skip to content

Commit e7391ea

Browse files
guybedforddanielleadams
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 ce2dc48 commit e7391ea

3 files changed

Lines changed: 91 additions & 94 deletions

File tree

doc/api/esm.md

Lines changed: 55 additions & 49 deletions

lib/internal/modules/esm/resolve.js

Lines 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,
@@ -384,9 +385,10 @@ const encodedSepRegEx = /%2F|%5C/i;
384385
/**
385386
* @param {URL} resolved
386387
* @param {string | URL | undefined} base
388+
* @param {boolean} preserveSymlinks
387389
* @returns {URL | undefined}
388390
*/
389-
function finalizeResolution(resolved, base) {
391+
function finalizeResolution(resolved, base, preserveSymlinks) {
390392
if (RegExpPrototypeTest(encodedSepRegEx, resolved.pathname))
391393
throw new ERR_INVALID_MODULE_SPECIFIER(
392394
resolved.pathname, 'must not include encoded "/" or "\\" characters',
@@ -417,6 +419,17 @@ function finalizeResolution(resolved, base) {
417419
path || resolved.pathname, base && fileURLToPath(base), 'module');
418420
}
419421

422+
if (!preserveSymlinks) {
423+
const real = realpathSync(path, {
424+
[internalFS.realpathCacheKey]: realpathCache
425+
});
426+
const { search, hash } = resolved;
427+
resolved =
428+
pathToFileURL(real + (StringPrototypeEndsWith(path, sep) ? '/' : ''));
429+
resolved.search = search;
430+
resolved.hash = hash;
431+
}
432+
420433
return resolved;
421434
}
422435

@@ -468,7 +481,8 @@ function throwInvalidPackageTarget(
468481
internal, base && fileURLToPath(base));
469482
}
470483

471-
const invalidSegmentRegEx = /(^|\\|\/)(\.\.?|node_modules)(\\|\/|$)/;
484+
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;
485+
const invalidPackageNameRegEx = /^\.|%|\\/;
472486
const patternRegEx = /\*/g;
473487

474488
function resolvePackageTargetString(
@@ -810,13 +824,9 @@ function parsePackageName(specifier, base) {
810824
specifier : StringPrototypeSlice(specifier, 0, separatorIndex);
811825

812826
// Package name cannot have leading . and cannot have percent-encoding or
813-
// separators.
814-
for (let i = 0; i < packageName.length; i++) {
815-
if (packageName[i] === '%' || packageName[i] === '\\') {
816-
validPackageName = false;
817-
break;
818-
}
819-
}
827+
// \\ separators.
828+
if (RegExpPrototypeExec(invalidPackageNameRegEx, packageName) !== null)
829+
validPackageName = false;
820830

821831
if (!validPackageName) {
822832
throw new ERR_INVALID_MODULE_SPECIFIER(
@@ -836,6 +846,9 @@ function parsePackageName(specifier, base) {
836846
* @returns {URL}
837847
*/
838848
function packageResolve(specifier, base, conditions) {
849+
if (NativeModule.canBeRequiredByUsers(specifier))
850+
return new URL('node:' + specifier);
851+
839852
const { packageName, packageSubpath, isScoped } =
840853
parsePackageName(specifier, base);
841854

@@ -912,9 +925,10 @@ function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) {
912925
* @param {string} specifier
913926
* @param {string | URL | undefined} base
914927
* @param {Set<string>} conditions
928+
* @param {boolean} preserveSymlinks
915929
* @returns {URL}
916930
*/
917-
function moduleResolve(specifier, base, conditions) {
931+
function moduleResolve(specifier, base, conditions, preserveSymlinks) {
918932
// Order swapped from spec for minor perf gain.
919933
// Ok since relative URLs cannot parse as URLs.
920934
let resolved;
@@ -929,7 +943,9 @@ function moduleResolve(specifier, base, conditions) {
929943
resolved = packageResolve(specifier, base, conditions);
930944
}
931945
}
932-
return finalizeResolution(resolved, base);
946+
if (resolved.protocol !== 'file:')
947+
return resolved;
948+
return finalizeResolution(resolved, base, preserveSymlinks);
933949
}
934950

935951
/**
@@ -1001,28 +1017,6 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
10011017
}
10021018
}
10031019
}
1004-
let parsed;
1005-
try {
1006-
parsed = new URL(specifier);
1007-
if (parsed.protocol === 'data:') {
1008-
return {
1009-
url: specifier
1010-
};
1011-
}
1012-
} catch {}
1013-
if (parsed && parsed.protocol === 'node:')
1014-
return { url: specifier };
1015-
if (parsed && parsed.protocol !== 'file:' && parsed.protocol !== 'data:')
1016-
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(parsed);
1017-
if (NativeModule.canBeRequiredByUsers(specifier)) {
1018-
return {
1019-
url: 'node:' + specifier
1020-
};
1021-
}
1022-
if (parentURL && StringPrototypeStartsWith(parentURL, 'data:')) {
1023-
// This is gonna blow up, we want the error
1024-
new URL(specifier, parentURL);
1025-
}
10261020

10271021
const isMain = parentURL === undefined;
10281022
if (isMain) {
@@ -1041,7 +1035,8 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
10411035
conditions = getConditionsSet(conditions);
10421036
let url;
10431037
try {
1044-
url = moduleResolve(specifier, parentURL, conditions);
1038+
url = moduleResolve(specifier, parentURL, conditions,
1039+
isMain ? preserveSymlinksMain : preserveSymlinks);
10451040
} catch (error) {
10461041
// Try to give the user a hint of what would have been the
10471042
// resolved CommonJS module
@@ -1065,17 +1060,9 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
10651060
throw error;
10661061
}
10671062

1068-
if (isMain ? !preserveSymlinksMain : !preserveSymlinks) {
1069-
const urlPath = fileURLToPath(url);
1070-
const real = realpathSync(urlPath, {
1071-
[internalFS.realpathCacheKey]: realpathCache
1072-
});
1073-
const old = url;
1074-
url = pathToFileURL(
1075-
real + (StringPrototypeEndsWith(urlPath, sep) ? '/' : ''));
1076-
url.search = old.search;
1077-
url.hash = old.hash;
1078-
}
1063+
if (url.protocol !== 'file:' && url.protocol !== 'data:' &&
1064+
url.protocol !== 'node:')
1065+
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url);
10791066

10801067
return { url: `${url}` };
10811068
}

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

Lines changed: 4 additions & 0 deletions

0 commit comments

Comments
 (0)