Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
lib: fix readdir in recursive mode with a buffer argument
calling readdir with a buffer arguemnt and the recursive flag
produces an ERR_INVALID_ARG_TYPE error. This is to fix that.

Fixes: #58892
  • Loading branch information
y-okt committed Dec 13, 2025
commit bb1084faef40b6556a3b1e6fb2d806d3d1a0d490
35 changes: 30 additions & 5 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -1394,6 +1394,31 @@ function readdirRecursive(basePath, options, callback) {
read(context.pathsQueue[i++]);
}

function joinPath(base, name) {
if (BufferIsBuffer(base)) {
const baseStr = BufferToString(base);
const nameStr = BufferIsBuffer(name) ? BufferToString(name) : name;
return Buffer.from(pathModule.join(baseStr, nameStr));
}
return pathModule.join(base, name);
}

function relativePath(from, to) {
if (BufferIsBuffer(from)) {
const fromStr = BufferToString(from);
const toStr = BufferIsBuffer(to) ? BufferToString(to) : to;
return Buffer.from(pathModule.relative(fromStr, toStr));
}
return pathModule.relative(from, to);
}

function toPathString(path) {
if (BufferIsBuffer(path)) {
return BufferToString(path);
}
return path;
}

// Calling `readdir` with `withFileTypes=true`, the result is an array of arrays.
// The first array is the names, and the second array is the types.
// They are guaranteed to be the same length; hence, setting `length` to the length
Expand All @@ -1407,21 +1432,21 @@ function handleDirents({ result, currentPath, context }) {
for (let i = 0; i < length; i++) {
// Avoid excluding symlinks, as they are not directories.
// Refs: https://github.com/nodejs/node/issues/52663
const fullPath = pathModule.join(currentPath, names[i]);
const fullPath = joinPath(currentPath, names[i]);
const dirent = getDirent(currentPath, names[i], types[i]);
ArrayPrototypePush(context.readdirResults, dirent);

if (dirent.isDirectory() || binding.internalModuleStat(fullPath) === 1) {
if (dirent.isDirectory() || binding.internalModuleStat(toPathString(fullPath)) === 1) {
ArrayPrototypePush(context.pathsQueue, fullPath);
}
}
}

function handleFilePaths({ result, currentPath, context }) {
for (let i = 0; i < result.length; i++) {
const resultPath = pathModule.join(currentPath, result[i]);
const relativeResultPath = pathModule.relative(context.basePath, resultPath);
const stat = binding.internalModuleStat(resultPath);
const resultPath = joinPath(currentPath, result[i]);
const relativeResultPath = relativePath(context.basePath, resultPath);
const stat = binding.internalModuleStat(toPathString(resultPath));
ArrayPrototypePush(context.readdirResults, relativeResultPath);

if (stat === 1) {
Expand Down
33 changes: 29 additions & 4 deletions lib/internal/fs/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,31 @@ async function mkdir(path, options) {
);
}

function joinPath(base, name) {
if (BufferIsBuffer(base)) {
const baseStr = BufferToString(base);
const nameStr = BufferIsBuffer(name) ? BufferToString(name) : name;
return Buffer.from(pathModule.join(baseStr, nameStr));
}
return pathModule.join(base, name);
}

function relativePath(from, to) {
if (BufferIsBuffer(from)) {
const fromStr = BufferToString(from);
const toStr = BufferIsBuffer(to) ? BufferToString(to) : to;
return Buffer.from(pathModule.relative(fromStr, toStr));
}
return pathModule.relative(from, to);
}

function toPathString(path) {
if (BufferIsBuffer(path)) {
return BufferToString(path);
}
return path;
}

async function readdirRecursive(originalPath, options) {
const result = [];
const queue = [
Expand All @@ -896,7 +921,7 @@ async function readdirRecursive(originalPath, options) {
for (const dirent of getDirents(path, readdir)) {
ArrayPrototypePush(result, dirent);
if (dirent.isDirectory()) {
const direntPath = pathModule.join(path, dirent.name);
const direntPath = joinPath(path, dirent.name);
ArrayPrototypePush(queue, [
direntPath,
await PromisePrototypeThen(
Expand All @@ -917,11 +942,11 @@ async function readdirRecursive(originalPath, options) {
while (queue.length > 0) {
const { 0: path, 1: readdir } = ArrayPrototypePop(queue);
for (const ent of readdir) {
const direntPath = pathModule.join(path, ent);
const stat = binding.internalModuleStat(direntPath);
const direntPath = joinPath(path, ent);
const stat = binding.internalModuleStat(toPathString(direntPath));
ArrayPrototypePush(
result,
pathModule.relative(originalPath, direntPath),
relativePath(originalPath, direntPath),
);
if (stat === 1) {
ArrayPrototypePush(queue, [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
'use strict';

// This test will fail because the the implementation does not properly
// handle the case when the path is a Buffer and the function is called
// in recursive mode.

// Test that fs.promises.readdir works with Buffer paths in recursive mode.
// Refs: https://github.com/nodejs/node/issues/58892

const common = require('../common');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
'use strict';

// This test will fail because the the implementation does not properly
// handle the case when the path is a Buffer and the function is called
// in recursive mode.

// Test that readdir callback works with Buffer paths in recursive mode.
// Refs: https://github.com/nodejs/node/issues/58892

const common = require('../common');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
'use strict';

// This test will fail because the the implementation does not properly
// handle the case when the path is a Buffer and the function is called
// in recursive mode.

// Test that readdirSync works with Buffer paths in recursive mode.
// Refs: https://github.com/nodejs/node/issues/58892

require('../common');
Expand Down