From 738144c311ce090789736c3932f2446bf3501fca Mon Sep 17 00:00:00 2001 From: Bradley Farias Date: Mon, 31 Oct 2022 14:53:58 -0500 Subject: [PATCH] module: ensure relative requires work from deleted directories PR-URL: https://github.com/nodejs/node/pull/42384 Reviewed-By: Geoffrey Booth Reviewed-By: James M Snell --- lib/internal/modules/cjs/loader.js | 50 +++++++++++++++++++----- test/parallel/test-require-enoent-dir.js | 30 ++++++++++++++ 2 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 test/parallel/test-require-enoent-dir.js diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 7e6cad0f6f3388..02a07222746dfd 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -130,9 +130,10 @@ const { validateString } = require('internal/validators'); const pendingDeprecation = getOptionValue('--pending-deprecation'); const { - CHAR_FORWARD_SLASH, CHAR_BACKWARD_SLASH, - CHAR_COLON + CHAR_COLON, + CHAR_DOT, + CHAR_FORWARD_SLASH, } = require('internal/constants'); const { @@ -538,7 +539,12 @@ function resolveExports(nmPath, request) { } } -const trailingSlashRegex = /(?:^|\/)\.?\.$/; +/** + * @param {string} request a relative or absolute file path + * @param {Array} paths file system directories to search as file paths + * @param {boolean} isMain if the request is the main app entry point + * @returns {string | false} + */ Module._findPath = function(request, paths, isMain) { const absoluteRequest = path.isAbsolute(request); if (absoluteRequest) { @@ -553,18 +559,42 @@ Module._findPath = function(request, paths, isMain) { return entry; let exts; - let trailingSlash = request.length > 0 && - StringPrototypeCharCodeAt(request, request.length - 1) === - CHAR_FORWARD_SLASH; - if (!trailingSlash) { - trailingSlash = RegExpPrototypeExec(trailingSlashRegex, request) !== null; + const trailingSlash = request.length > 0 && + (StringPrototypeCharCodeAt(request, request.length - 1) === CHAR_FORWARD_SLASH || ( + StringPrototypeCharCodeAt(request, request.length - 1) === CHAR_DOT && + ( + request.length === 1 || + StringPrototypeCharCodeAt(request, request.length - 2) === CHAR_FORWARD_SLASH || + (StringPrototypeCharCodeAt(request, request.length - 2) === CHAR_DOT && ( + request.length === 2 || + StringPrototypeCharCodeAt(request, request.length - 3) === CHAR_FORWARD_SLASH + )) + ) + )); + + const isRelative = StringPrototypeCharCodeAt(request, 0) === CHAR_DOT && + ( + request.length === 1 || + StringPrototypeCharCodeAt(request, 1) === CHAR_FORWARD_SLASH || + (isWindows && StringPrototypeCharCodeAt(request, 1) === CHAR_BACKWARD_SLASH) || + (StringPrototypeCharCodeAt(request, 1) === CHAR_DOT && (( + request.length === 2 || + StringPrototypeCharCodeAt(request, 2) === CHAR_FORWARD_SLASH) || + (isWindows && StringPrototypeCharCodeAt(request, 2) === CHAR_BACKWARD_SLASH))) + ); + let insidePath = true; + if (isRelative) { + const normalizedRequest = path.normalize(request); + if (StringPrototypeStartsWith(normalizedRequest, '..')) { + insidePath = false; + } } // For each path for (let i = 0; i < paths.length; i++) { - // Don't search further if path doesn't exist + // Don't search further if path doesn't exist and request is inside the path const curPath = paths[i]; - if (curPath && _stat(curPath) < 1) continue; + if (insidePath && curPath && _stat(curPath) < 1) continue; if (!absoluteRequest) { const exportsResolved = resolveExports(curPath, request); diff --git a/test/parallel/test-require-enoent-dir.js b/test/parallel/test-require-enoent-dir.js new file mode 100644 index 00000000000000..519c3e4ea720a5 --- /dev/null +++ b/test/parallel/test-require-enoent-dir.js @@ -0,0 +1,30 @@ +'use strict'; + +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +tmpdir.refresh(); + +const fooPath = path.join(tmpdir.path, 'foo.cjs'); +fs.writeFileSync(fooPath, ''); + +const dirPath = path.join(tmpdir.path, 'delete_me'); +fs.mkdirSync(dirPath, { + recursive: true +}); + +const barPath = path.join(dirPath, 'bar.cjs'); +fs.writeFileSync(barPath, ` + module.exports = () => require('../foo.cjs').call() +`); + +const foo = require(fooPath); +const unique = Symbol('unique'); +foo.call = common.mustCall(() => unique); +const bar = require(barPath); + +fs.rmSync(dirPath, { recursive: true }); +assert.strict.equal(bar(), unique);