Repository: denoland/deno
Pull request: fix(ext/node): validate readlink arguments

Also accepts `Buffer` typed path. Allows https://github.com/nodejs/node/blob/v24.2.0/test/parallel/test-fs-readlink-type-check.js test to pass.

Towards #29972

Changed files:
filename: ext/node/polyfills/_fs/_fs_readlink.ts
patch: @@ -5,9 +5,11 @@
 
 import { TextEncoder } from "ext:deno_web/08_text_encoding.js";
 import { MaybeEmpty, notImplemented } from "ext:deno_node/_utils.ts";
-import { pathFromURL } from "ext:deno_web/00_infra.js";
 import { promisify } from "ext:deno_node/internal/util.mjs";
 import { denoErrorToNodeError } from "ext:deno_node/internal/errors.ts";
+import { Buffer } from "node:buffer";
+import { getValidatedPathToString } from "ext:deno_node/internal/fs/utils.mjs";
+import { makeCallback } from "ext:deno_node/_fs/_fs_common.ts";
 
 type ReadlinkCallback = (
   err: MaybeEmpty<Error>,
@@ -51,18 +53,19 @@ function getEncoding(
 }
 
 export function readlink(
-  path: string | URL,
+  path: string | Buffer | URL,
   optOrCallback: ReadlinkCallback | ReadlinkOptions,
   callback?: ReadlinkCallback,
 ) {
-  path = path instanceof URL ? pathFromURL(path) : path;
+  path = getValidatedPathToString(path);
 
   let cb: ReadlinkCallback | undefined;
   if (typeof optOrCallback === "function") {
     cb = optOrCallback;
   } else {
     cb = callback;
   }
+  cb = makeCallback(cb);
 
   const encoding = getEncoding(optOrCallback);
 
@@ -80,15 +83,15 @@ export function readlink(
 }
 
 export const readlinkPromise = promisify(readlink) as (
-  path: string | URL,
+  path: string | Buffer | URL,
   opt?: ReadlinkOptions,
 ) => Promise<string | Uint8Array>;
 
 export function readlinkSync(
-  path: string | URL,
+  path: string | Buffer | URL,
   opt?: ReadlinkOptions,
 ): string | Uint8Array {
-  path = path instanceof URL ? pathFromURL(path) : path;
+  path = getValidatedPathToString(path);
 
   try {
     return maybeEncode(Deno.readLinkSync(path), getEncoding(opt));

filename: tests/node_compat/config.toml
patch: @@ -470,6 +470,7 @@
 "parallel/test-fs-readfile-unlink.js" = {}
 "parallel/test-fs-readfile-zero-byte-liar.js" = {}
 "parallel/test-fs-readfilesync-enoent.js" = {}
+"parallel/test-fs-readlink-type-check.js" = {}
 "parallel/test-fs-readv-promisify.js" = {}
 "parallel/test-fs-ready-event-stream.js" = {}
 "parallel/test-fs-rename-type-check.js" = {}

filename: tests/unit_node/_fs/_fs_readlink_test.ts
patch: @@ -3,6 +3,7 @@ import { assertCallbackErrorUncaught } from "../_test_utils.ts";
 import { readlink, readlinkSync } from "node:fs";
 import { assert, assertEquals } from "@std/assert";
 import * as path from "@std/path";
+import { Buffer } from "node:buffer";
 
 const testDir = Deno.makeTempDirSync();
 const oldname = path.join(testDir, "oldname");
@@ -73,3 +74,20 @@ Deno.test("[std/node/fs] readlink callback isn't called twice if error is thrown
     invocation: `readlink(${JSON.stringify(newname)}, `,
   });
 });
+
+Deno.test("[node/fs] readlink accepts Buffer as path", async () => {
+  const data = await new Promise((res, rej) => {
+    readlink(Buffer.from(newname), (err, data) => {
+      if (err) {
+        rej(err);
+      }
+      res(data);
+    });
+  });
+  assertEquals(data, oldname);
+});
+
+Deno.test("[node/fs] readlinkSync accepts Buffer as path", () => {
+  const data = readlinkSync(Buffer.from(newname));
+  assertEquals(data, oldname);
+});