Skip to content

Commit dcc9474

Browse files
authored
fix(ext/node): fs.readFile, fs.readFileSync assert encoding (#30830)
Towards #29972 - Validates the encoding of `readFile`. - Fixes the handling of `binary` encoding where previously it returns Buffer. On Node.js, Buffer is only returned when the encoding is not specified. - Allows [parallel/test-fs-read-file-assert-encoding.js](https://github.com/nodejs/node/blob/v24.2.0/test/parallel/test-fs-read-file-assert-encoding.js) test to pass.
1 parent e7f1793 commit dcc9474

File tree

4 files changed

+71
-23
lines changed

4 files changed

+71
-23
lines changed

ext/node/polyfills/_fs/_fs_common.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
TextEncodings,
1717
} from "ext:deno_node/_utils.ts";
1818
import { type Buffer } from "node:buffer";
19+
import { assertEncoding } from "ext:deno_node/internal/fs/utils.mjs";
1920

2021
export type CallbackWithError = (err: ErrnoException | null) => void;
2122

@@ -57,6 +58,21 @@ export function isFileOptions(
5758
);
5859
}
5960

61+
export function getValidatedEncoding(
62+
optOrCallback?:
63+
| FileOptions
64+
| WriteFileOptions
65+
| ((...args: unknown[]) => unknown)
66+
| Encodings
67+
| null,
68+
): Encodings | null {
69+
const encoding = getEncoding(optOrCallback);
70+
if (encoding) {
71+
assertEncoding(encoding);
72+
}
73+
return encoding;
74+
}
75+
6076
export function getEncoding(
6177
optOrCallback?:
6278
| FileOptions

ext/node/polyfills/_fs/_fs_readFile.ts

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,29 @@
66
import {
77
BinaryOptionsArgument,
88
FileOptionsArgument,
9-
getEncoding,
109
getSignal,
10+
getValidatedEncoding,
1111
TextOptionsArgument,
1212
} from "ext:deno_node/_fs/_fs_common.ts";
1313
import { Buffer } from "node:buffer";
1414
import { readAll, readAllSync } from "ext:deno_io/12_io.js";
1515
import { FileHandle } from "ext:deno_node/internal/fs/handle.ts";
1616
import { pathFromURL } from "ext:deno_web/00_infra.js";
17-
import {
18-
BinaryEncodings,
19-
Encodings,
20-
TextEncodings,
21-
} from "ext:deno_node/_utils.ts";
17+
import { Encodings } from "ext:deno_node/_utils.ts";
2218
import { FsFile } from "ext:deno_fs/30_fs.js";
2319
import { denoErrorToNodeError } from "ext:deno_node/internal/errors.ts";
2420

25-
function maybeDecode(data: Uint8Array, encoding: TextEncodings): string;
21+
function maybeDecode(data: Uint8Array, encoding: Encodings): string;
2622
function maybeDecode(
2723
data: Uint8Array,
28-
encoding: BinaryEncodings | null,
24+
encoding: null,
2925
): Buffer;
3026
function maybeDecode(
3127
data: Uint8Array,
3228
encoding: Encodings | null,
3329
): string | Buffer {
3430
const buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
35-
if (encoding && encoding !== "binary") return buffer.toString(encoding);
31+
if (encoding) return buffer.toString(encoding);
3632
return buffer;
3733
}
3834

@@ -71,7 +67,7 @@ export function readFile(
7167
cb = callback;
7268
}
7369

74-
const encoding = getEncoding(optOrCallback);
70+
const encoding = getValidatedEncoding(optOrCallback);
7571
const signal = getSignal(optOrCallback);
7672

7773
let p: Promise<Uint8Array>;
@@ -87,12 +83,8 @@ export function readFile(
8783

8884
if (cb) {
8985
p.then((data: Uint8Array) => {
90-
if (encoding && encoding !== "binary") {
91-
const text = maybeDecode(data, encoding);
92-
return (cb as TextCallback)(null, text);
93-
}
94-
const buffer = maybeDecode(data, encoding);
95-
(cb as BinaryCallback)(null, buffer);
86+
const textOrBuffer = maybeDecode(data, encoding);
87+
(cb as BinaryCallback)(null, textOrBuffer);
9688
}, (err) => cb && cb(denoErrorToNodeError(err, { path, syscall: "open" })));
9789
}
9890
}
@@ -134,11 +126,7 @@ export function readFileSync(
134126
throw denoErrorToNodeError(err, { path, syscall: "open" });
135127
}
136128
}
137-
const encoding = getEncoding(opt);
138-
if (encoding && encoding !== "binary") {
139-
const text = maybeDecode(data, encoding);
140-
return text;
141-
}
142-
const buffer = maybeDecode(data, encoding);
143-
return buffer;
129+
const encoding = getValidatedEncoding(opt);
130+
const textOrBuffer = maybeDecode(data, encoding);
131+
return textOrBuffer;
144132
}

tests/node_compat/config.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@
454454
"parallel/test-fs-promises-readfile-empty.js" = {}
455455
"parallel/test-fs-promisified.js" = {}
456456
"parallel/test-fs-read-empty-buffer.js" = {}
457+
"parallel/test-fs-read-file-assert-encoding.js" = {}
457458
"parallel/test-fs-read-file-sync-hostname.js" = {}
458459
"parallel/test-fs-read-stream-autoClose.js" = {}
459460
"parallel/test-fs-read-stream-double-close.js" = {}

tests/unit_node/_fs/_fs_readFile_test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { assertCallbackErrorUncaught } from "../_test_utils.ts";
33
import { promises, readFile, readFileSync } from "node:fs";
44
import * as path from "@std/path";
55
import { assert, assertEquals, assertMatch } from "@std/assert";
6+
import { Buffer } from "node:buffer";
67

78
const moduleDir = path.dirname(path.fromFileUrl(import.meta.url));
89
const testData = path.resolve(moduleDir, "testdata", "hello.txt");
@@ -144,3 +145,45 @@ Deno.test("fs.readFileSync error message contains path + syscall", () => {
144145
}
145146
}
146147
});
148+
149+
Deno.test("fs.readFile returns Buffer when encoding is not provided", async () => {
150+
const data = await new Promise<Uint8Array>((res, rej) => {
151+
readFile(testData, (err, data) => {
152+
if (err) {
153+
rej(err);
154+
}
155+
res(data as Uint8Array);
156+
});
157+
});
158+
159+
assert(data instanceof Uint8Array);
160+
assertEquals(Buffer.isBuffer(data), true);
161+
assertEquals(data.toString(), "hello world");
162+
});
163+
164+
Deno.test("fs.readFile binary encoding returns string", async () => {
165+
const data = await new Promise<string>((res, rej) => {
166+
readFile(testData, { encoding: "binary" }, (err, data) => {
167+
if (err) {
168+
rej(err);
169+
}
170+
res(data as string);
171+
});
172+
});
173+
174+
assertEquals(typeof data, "string");
175+
assertEquals(data, "hello world");
176+
});
177+
178+
Deno.test("fs.readFileSync returns Buffer when encoding is not provided", () => {
179+
const data = readFileSync(testData);
180+
assert(data instanceof Uint8Array);
181+
assertEquals(Buffer.isBuffer(data), true);
182+
assertEquals(data.toString(), "hello world");
183+
});
184+
185+
Deno.test("fs.readFileSync binary encoding returns string", () => {
186+
const data = readFileSync(testData, { encoding: "binary" });
187+
assertEquals(typeof data, "string");
188+
assertEquals(data, "hello world");
189+
});

0 commit comments

Comments
 (0)