From 927352bd4e7f58458daa56921cf148862af05383 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Wed, 27 Nov 2024 08:35:39 +0100 Subject: [PATCH] fix(node/fs): add missing stat path argument validation (#27086) We didn't validate the `path` argument that's passed to `fs.stat()` and `fs.statSync()` which lead to wrong errors being thrown. The `@rollup/plugin-node-resolve` code calls it with `undefined` quite a lot which lead to `nitro` and `nuxt` failing. Fixes https://github.com/denoland/deno/issues/26700 --------- Co-authored-by: Yoshiya Hinosawa --- ext/node/polyfills/_fs/_fs_stat.ts | 4 +++ tests/unit_node/_fs/_fs_stat_test.ts | 37 +++++++++++++++++++++++++++- tests/unit_node/fs_test.ts | 5 +++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/ext/node/polyfills/_fs/_fs_stat.ts b/ext/node/polyfills/_fs/_fs_stat.ts index 507cb05eaf..f264746686 100644 --- a/ext/node/polyfills/_fs/_fs_stat.ts +++ b/ext/node/polyfills/_fs/_fs_stat.ts @@ -6,6 +6,7 @@ import { denoErrorToNodeError } from "ext:deno_node/internal/errors.ts"; import { promisify } from "ext:deno_node/internal/util.mjs"; import { primordials } from "ext:core/mod.js"; +import { getValidatedPath } from "ext:deno_node/internal/fs/utils.mjs"; const { ObjectCreate, ObjectAssign } = primordials; @@ -379,6 +380,7 @@ export function stat( ? optionsOrCallback : { bigint: false }; + path = getValidatedPath(path).toString(); if (!callback) throw new Error("No callback function supplied"); Deno.stat(path).then( @@ -409,6 +411,8 @@ export function statSync( path: string | URL, options: statOptions = { bigint: false, throwIfNoEntry: true }, ): Stats | BigIntStats | undefined { + path = getValidatedPath(path).toString(); + try { const origin = Deno.statSync(path); return CFISBIS(origin, options.bigint); diff --git a/tests/unit_node/_fs/_fs_stat_test.ts b/tests/unit_node/_fs/_fs_stat_test.ts index e42aa34a9a..3cbbe940b0 100644 --- a/tests/unit_node/_fs/_fs_stat_test.ts +++ b/tests/unit_node/_fs/_fs_stat_test.ts @@ -1,7 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import { assertCallbackErrorUncaught } from "../_test_utils.ts"; import { BigIntStats, stat, Stats, statSync } from "node:fs"; -import { assertEquals, fail } from "@std/assert"; +import { assert, assertEquals, fail } from "@std/assert"; export function assertStats(actual: Stats, expected: Deno.FileInfo) { assertEquals(actual.dev, expected.dev); @@ -152,3 +152,38 @@ Deno.test({ assertEquals(stats.isSocket(), false); }, }); + +Deno.test({ + name: "[node/fs] stat invalid path error", + async fn() { + try { + await new Promise((resolve, reject) => { + stat( + // deno-lint-ignore no-explicit-any + undefined as any, + (err, stats) => err ? reject(err) : resolve(stats), + ); + }); + fail(); + } catch (err) { + assert(err instanceof TypeError); + // deno-lint-ignore no-explicit-any + assertEquals((err as any).code, "ERR_INVALID_ARG_TYPE"); + } + }, +}); + +Deno.test({ + name: "[node/fs] statSync invalid path error", + fn() { + try { + // deno-lint-ignore no-explicit-any + statSync(undefined as any); + fail(); + } catch (err) { + assert(err instanceof TypeError); + // deno-lint-ignore no-explicit-any + assertEquals((err as any).code, "ERR_INVALID_ARG_TYPE"); + } + }, +}); diff --git a/tests/unit_node/fs_test.ts b/tests/unit_node/fs_test.ts index 631608d7cc..32bea40e75 100644 --- a/tests/unit_node/fs_test.ts +++ b/tests/unit_node/fs_test.ts @@ -3,6 +3,7 @@ /// import { assert, assertEquals, assertRejects, assertThrows } from "@std/assert"; import { join } from "node:path"; +import { fileURLToPath } from "node:url"; import { tmpdir } from "node:os"; import { closeSync, @@ -160,7 +161,9 @@ Deno.test( } catch (error: unknown) { assertEquals( `${error}`, - `Error: ENOENT: no such file or directory, stat '${fileUrl.pathname}'`, + `Error: ENOENT: no such file or directory, stat '${ + fileURLToPath(fileUrl) + }'`, ); } },