mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
fix(node/fs): node:fs.read and write should accept typed arrays other than Uint8Array (#25030)
Part of #25028. Our underlying read/write operations in `io` assume the buffer is a Uint8Array, but we were passing in other typed arrays (in the case above it was `Int8Array`).
This commit is contained in:
parent
c765d9ad2f
commit
1f2d48cd97
6 changed files with 106 additions and 19 deletions
|
@ -38,7 +38,7 @@ export type BinaryOptionsArgument =
|
||||||
export type FileOptionsArgument = Encodings | FileOptions;
|
export type FileOptionsArgument = Encodings | FileOptions;
|
||||||
|
|
||||||
export type ReadOptions = {
|
export type ReadOptions = {
|
||||||
buffer: Buffer | Uint8Array;
|
buffer: Buffer | ArrayBufferView;
|
||||||
offset: number;
|
offset: number;
|
||||||
length: number;
|
length: number;
|
||||||
position: number | null;
|
position: number | null;
|
||||||
|
|
|
@ -9,6 +9,7 @@ import * as io from "ext:deno_io/12_io.js";
|
||||||
import * as fs from "ext:deno_fs/30_fs.js";
|
import * as fs from "ext:deno_fs/30_fs.js";
|
||||||
import { ReadOptions } from "ext:deno_node/_fs/_fs_common.ts";
|
import { ReadOptions } from "ext:deno_node/_fs/_fs_common.ts";
|
||||||
import {
|
import {
|
||||||
|
arrayBufferViewToUint8Array,
|
||||||
validateOffsetLengthRead,
|
validateOffsetLengthRead,
|
||||||
validatePosition,
|
validatePosition,
|
||||||
} from "ext:deno_node/internal/fs/utils.mjs";
|
} from "ext:deno_node/internal/fs/utils.mjs";
|
||||||
|
@ -16,6 +17,7 @@ import {
|
||||||
validateBuffer,
|
validateBuffer,
|
||||||
validateInteger,
|
validateInteger,
|
||||||
} from "ext:deno_node/internal/validators.mjs";
|
} from "ext:deno_node/internal/validators.mjs";
|
||||||
|
import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts";
|
||||||
|
|
||||||
type readSyncOptions = {
|
type readSyncOptions = {
|
||||||
offset: number;
|
offset: number;
|
||||||
|
@ -38,7 +40,7 @@ export function read(
|
||||||
): void;
|
): void;
|
||||||
export function read(
|
export function read(
|
||||||
fd: number,
|
fd: number,
|
||||||
buffer: Buffer | Uint8Array,
|
buffer: ArrayBufferView,
|
||||||
offset: number,
|
offset: number,
|
||||||
length: number,
|
length: number,
|
||||||
position: number | null,
|
position: number | null,
|
||||||
|
@ -46,7 +48,7 @@ export function read(
|
||||||
): void;
|
): void;
|
||||||
export function read(
|
export function read(
|
||||||
fd: number,
|
fd: number,
|
||||||
optOrBufferOrCb?: Buffer | Uint8Array | ReadOptions | Callback,
|
optOrBufferOrCb?: ArrayBufferView | ReadOptions | Callback,
|
||||||
offsetOrCallback?: number | Callback,
|
offsetOrCallback?: number | Callback,
|
||||||
length?: number,
|
length?: number,
|
||||||
position?: number | null,
|
position?: number | null,
|
||||||
|
@ -75,9 +77,9 @@ export function read(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
optOrBufferOrCb instanceof Buffer || optOrBufferOrCb instanceof Uint8Array
|
isArrayBufferView(optOrBufferOrCb)
|
||||||
) {
|
) {
|
||||||
buffer = optOrBufferOrCb;
|
buffer = arrayBufferViewToUint8Array(optOrBufferOrCb);
|
||||||
} else if (typeof optOrBufferOrCb === "function") {
|
} else if (typeof optOrBufferOrCb === "function") {
|
||||||
offset = 0;
|
offset = 0;
|
||||||
buffer = Buffer.alloc(16384);
|
buffer = Buffer.alloc(16384);
|
||||||
|
@ -86,7 +88,7 @@ export function read(
|
||||||
} else {
|
} else {
|
||||||
const opt = optOrBufferOrCb as ReadOptions;
|
const opt = optOrBufferOrCb as ReadOptions;
|
||||||
if (
|
if (
|
||||||
!(opt.buffer instanceof Buffer) && !(opt.buffer instanceof Uint8Array)
|
!isArrayBufferView(opt.buffer)
|
||||||
) {
|
) {
|
||||||
throw new ERR_INVALID_ARG_TYPE("buffer", [
|
throw new ERR_INVALID_ARG_TYPE("buffer", [
|
||||||
"Buffer",
|
"Buffer",
|
||||||
|
@ -94,8 +96,12 @@ export function read(
|
||||||
"DataView",
|
"DataView",
|
||||||
], optOrBufferOrCb);
|
], optOrBufferOrCb);
|
||||||
}
|
}
|
||||||
|
if (opt.buffer === undefined) {
|
||||||
|
buffer = Buffer.alloc(16384);
|
||||||
|
} else {
|
||||||
|
buffer = arrayBufferViewToUint8Array(opt.buffer);
|
||||||
|
}
|
||||||
offset = opt.offset ?? 0;
|
offset = opt.offset ?? 0;
|
||||||
buffer = opt.buffer ?? Buffer.alloc(16384);
|
|
||||||
length = opt.length ?? buffer.byteLength;
|
length = opt.length ?? buffer.byteLength;
|
||||||
position = opt.position ?? null;
|
position = opt.position ?? null;
|
||||||
}
|
}
|
||||||
|
@ -131,19 +137,19 @@ export function read(
|
||||||
|
|
||||||
export function readSync(
|
export function readSync(
|
||||||
fd: number,
|
fd: number,
|
||||||
buffer: Buffer | Uint8Array,
|
buffer: ArrayBufferView,
|
||||||
offset: number,
|
offset: number,
|
||||||
length: number,
|
length: number,
|
||||||
position: number | null,
|
position: number | null,
|
||||||
): number;
|
): number;
|
||||||
export function readSync(
|
export function readSync(
|
||||||
fd: number,
|
fd: number,
|
||||||
buffer: Buffer | Uint8Array,
|
buffer: ArrayBufferView,
|
||||||
opt: readSyncOptions,
|
opt: readSyncOptions,
|
||||||
): number;
|
): number;
|
||||||
export function readSync(
|
export function readSync(
|
||||||
fd: number,
|
fd: number,
|
||||||
buffer: Buffer | Uint8Array,
|
buffer: ArrayBufferView,
|
||||||
offsetOrOpt?: number | readSyncOptions,
|
offsetOrOpt?: number | readSyncOptions,
|
||||||
length?: number,
|
length?: number,
|
||||||
position?: number | null,
|
position?: number | null,
|
||||||
|
@ -156,6 +162,8 @@ export function readSync(
|
||||||
|
|
||||||
validateBuffer(buffer);
|
validateBuffer(buffer);
|
||||||
|
|
||||||
|
buffer = arrayBufferViewToUint8Array(buffer);
|
||||||
|
|
||||||
if (length == null) {
|
if (length == null) {
|
||||||
length = 0;
|
length = 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
import * as io from "ext:deno_io/12_io.js";
|
import * as io from "ext:deno_io/12_io.js";
|
||||||
import * as fs from "ext:deno_fs/30_fs.js";
|
import * as fs from "ext:deno_fs/30_fs.js";
|
||||||
import {
|
import {
|
||||||
|
arrayBufferViewToUint8Array,
|
||||||
getValidatedFd,
|
getValidatedFd,
|
||||||
validateOffsetLengthWrite,
|
validateOffsetLengthWrite,
|
||||||
validateStringAfterArrayBufferView,
|
validateStringAfterArrayBufferView,
|
||||||
|
@ -23,9 +24,7 @@ export function writeSync(fd, buffer, offset, length, position) {
|
||||||
fd = getValidatedFd(fd);
|
fd = getValidatedFd(fd);
|
||||||
|
|
||||||
const innerWriteSync = (fd, buffer, offset, length, position) => {
|
const innerWriteSync = (fd, buffer, offset, length, position) => {
|
||||||
if (buffer instanceof DataView) {
|
buffer = arrayBufferViewToUint8Array(buffer);
|
||||||
buffer = new Uint8Array(buffer.buffer);
|
|
||||||
}
|
|
||||||
if (typeof position === "number") {
|
if (typeof position === "number") {
|
||||||
fs.seekSync(fd, position, io.SeekMode.Start);
|
fs.seekSync(fd, position, io.SeekMode.Start);
|
||||||
}
|
}
|
||||||
|
@ -69,9 +68,7 @@ export function write(fd, buffer, offset, length, position, callback) {
|
||||||
fd = getValidatedFd(fd);
|
fd = getValidatedFd(fd);
|
||||||
|
|
||||||
const innerWrite = async (fd, buffer, offset, length, position) => {
|
const innerWrite = async (fd, buffer, offset, length, position) => {
|
||||||
if (buffer instanceof DataView) {
|
buffer = arrayBufferViewToUint8Array(buffer);
|
||||||
buffer = new Uint8Array(buffer.buffer);
|
|
||||||
}
|
|
||||||
if (typeof position === "number") {
|
if (typeof position === "number") {
|
||||||
await fs.seek(fd, position, io.SeekMode.Start);
|
await fs.seek(fd, position, io.SeekMode.Start);
|
||||||
}
|
}
|
||||||
|
|
|
@ -986,6 +986,16 @@ export const validatePosition = hideStackFrames((position) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** @type {(buffer: ArrayBufferView) => Uint8Array} */
|
||||||
|
export const arrayBufferViewToUint8Array = hideStackFrames(
|
||||||
|
(buffer) => {
|
||||||
|
if (!(buffer instanceof Uint8Array)) {
|
||||||
|
return new Uint8Array(buffer.buffer);
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const realpathCacheKey = Symbol("realpathCacheKey");
|
export const realpathCacheKey = Symbol("realpathCacheKey");
|
||||||
export const constants = {
|
export const constants = {
|
||||||
kIoMaxLength,
|
kIoMaxLength,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
/// <reference types="npm:@types/node" />
|
||||||
import {
|
import {
|
||||||
assertEquals,
|
assertEquals,
|
||||||
assertFalse,
|
assertFalse,
|
||||||
|
@ -13,21 +14,21 @@ import { closeSync } from "node:fs";
|
||||||
|
|
||||||
async function readTest(
|
async function readTest(
|
||||||
testData: string,
|
testData: string,
|
||||||
buffer: Buffer,
|
buffer: NodeJS.ArrayBufferView,
|
||||||
offset: number,
|
offset: number,
|
||||||
length: number,
|
length: number,
|
||||||
position: number | null = null,
|
position: number | null = null,
|
||||||
expected: (
|
expected: (
|
||||||
fd: number,
|
fd: number,
|
||||||
bytesRead: number | null,
|
bytesRead: number | null,
|
||||||
data: Buffer | undefined,
|
data: ArrayBufferView | undefined,
|
||||||
) => void,
|
) => void,
|
||||||
) {
|
) {
|
||||||
let fd1 = 0;
|
let fd1 = 0;
|
||||||
await new Promise<{
|
await new Promise<{
|
||||||
fd: number;
|
fd: number;
|
||||||
bytesRead: number | null;
|
bytesRead: number | null;
|
||||||
data: Buffer | undefined;
|
data: ArrayBufferView | undefined;
|
||||||
}>((resolve, reject) => {
|
}>((resolve, reject) => {
|
||||||
open(testData, "r", (err, fd) => {
|
open(testData, "r", (err, fd) => {
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
|
@ -320,3 +321,35 @@ Deno.test({
|
||||||
closeSync(fd);
|
closeSync(fd);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "accepts non Uint8Array buffer",
|
||||||
|
async fn() {
|
||||||
|
const moduleDir = path.dirname(path.fromFileUrl(import.meta.url));
|
||||||
|
const testData = path.resolve(moduleDir, "testdata", "hello.txt");
|
||||||
|
const buffer = new ArrayBuffer(1024);
|
||||||
|
const buf = new Int8Array(buffer);
|
||||||
|
await readTest(
|
||||||
|
testData,
|
||||||
|
buf,
|
||||||
|
buf.byteOffset,
|
||||||
|
buf.byteLength,
|
||||||
|
null,
|
||||||
|
(_fd, bytesRead, data) => {
|
||||||
|
assertStrictEquals(bytesRead, 11);
|
||||||
|
assertEquals(data instanceof Buffer, true);
|
||||||
|
assertMatch((data as Buffer).toString(), /hello world/);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const fd = openSync(testData, "r");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const nRead = readSync(fd, buf);
|
||||||
|
const expected = new TextEncoder().encode("hello world");
|
||||||
|
|
||||||
|
assertEquals(buf.slice(0, nRead), new Int8Array(expected.buffer));
|
||||||
|
} finally {
|
||||||
|
closeSync(fd);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
@ -73,3 +73,42 @@ Deno.test({
|
||||||
assertEquals(decoder.decode(data), "\x00\x00\x00\x00hello world");
|
assertEquals(decoder.decode(data), "\x00\x00\x00\x00hello world");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "Accepts non Uint8Array buffer",
|
||||||
|
async fn() {
|
||||||
|
const tempFile: string = Deno.makeTempFileSync();
|
||||||
|
using file = Deno.openSync(tempFile, {
|
||||||
|
create: true,
|
||||||
|
write: true,
|
||||||
|
read: true,
|
||||||
|
});
|
||||||
|
const bytes = [0, 1, 2, 3, 4];
|
||||||
|
const buffer = new Int8Array(bytes);
|
||||||
|
for (let i = 0; i < buffer.length; i++) {
|
||||||
|
buffer[i] = i;
|
||||||
|
}
|
||||||
|
let nWritten = writeSync(file.rid, buffer);
|
||||||
|
|
||||||
|
const data = Deno.readFileSync(tempFile);
|
||||||
|
|
||||||
|
assertEquals(nWritten, 5);
|
||||||
|
assertEquals(data, new Uint8Array(bytes));
|
||||||
|
|
||||||
|
nWritten = await new Promise((resolve, reject) =>
|
||||||
|
write(
|
||||||
|
file.rid,
|
||||||
|
buffer,
|
||||||
|
0,
|
||||||
|
5,
|
||||||
|
(err: unknown, nwritten: number) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
resolve(nwritten);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(nWritten, 5);
|
||||||
|
assertEquals(data, new Uint8Array(bytes));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue