diff --git a/std/node/buffer.ts b/std/node/buffer.ts index fa911ee6f77c48..b9b33be99305cd 100644 --- a/std/node/buffer.ts +++ b/std/node/buffer.ts @@ -1,3 +1,38 @@ +import * as hex from "../encoding/hex.ts"; +import * as base64 from "../encoding/base64.ts"; +import { notImplemented } from "./_utils.ts"; + +const validEncodings = ["utf8", "hex", "base64"]; +const notImplementedEncodings = [ + "utf16le", + "latin1", + "ascii", + "binary", + "ucs2", +]; + +function checkEncoding(encoding = "utf8", strict = true): string { + if (typeof encoding !== "string" || (strict && encoding === "")) { + if (!strict) return "utf8"; + throw new TypeError(`Unkown encoding: ${encoding}`); + } + + encoding = encoding.toLowerCase(); + if (encoding === "utf-8" || encoding === "") { + return "utf8"; + } + + if (notImplementedEncodings.includes(encoding)) { + notImplemented(`"${encoding}" encoding`); + } + + if (!validEncodings.includes(encoding)) { + throw new TypeError(`Unkown encoding: ${encoding}`); + } + + return encoding; +} + /** * See also https://nodejs.org/api/buffer.html */ @@ -66,11 +101,24 @@ export default class Buffer extends Uint8Array { /** * Creates a new Buffer containing string. */ - static from(string: string): Buffer; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - static from(value: any, offset?: number, length?: number): Buffer { - if (typeof value == "string") + static from(string: string, encoding?: string): Buffer; + static from( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: any, + offsetOrEncoding?: number | string, + length?: number + ): Buffer { + const offset = + typeof offsetOrEncoding === "string" ? undefined : offsetOrEncoding; + let encoding = + typeof offsetOrEncoding === "string" ? offsetOrEncoding : undefined; + + if (typeof value == "string") { + encoding = checkEncoding(encoding, false); + if (encoding === "hex") return new Buffer(hex.decodeString(value).buffer); + if (encoding === "base64") return new Buffer(base64.decode(value)); return new Buffer(new TextEncoder().encode(value).buffer); + } // workaround for https://github.com/microsoft/TypeScript/issues/38446 return new Buffer(value, offset!, length); @@ -246,7 +294,13 @@ export default class Buffer extends Uint8Array { * encoding. start and end may be passed to decode only a subset of buf. */ toString(encoding = "utf8", start = 0, end = this.length): string { - return new TextDecoder(encoding).decode(this.subarray(start, end)); + encoding = checkEncoding(encoding); + + const b = this.subarray(start, end); + if (encoding === "hex") return hex.encodeToString(b); + if (encoding === "base64") return base64.encode(b.buffer); + + return new TextDecoder(encoding).decode(b); } /** diff --git a/std/node/buffer_test.ts b/std/node/buffer_test.ts index d2b41fa1b24589..adbc7975155a72 100644 --- a/std/node/buffer_test.ts +++ b/std/node/buffer_test.ts @@ -116,6 +116,150 @@ Deno.test({ }, }); +Deno.test({ + name: "Buffer from string hex", + fn() { + for (const encoding of ["hex", "HEX"]) { + const buffer: Buffer = Buffer.from( + "7468697320697320612074c3a97374", + encoding + ); + assertEquals(buffer.length, 15, "Buffer length should be 15"); + assertEquals( + buffer.toString(), + "this is a tést", + "Buffer to string should recover the string" + ); + } + }, +}); + +Deno.test({ + name: "Buffer from string base64", + fn() { + for (const encoding of ["base64", "BASE64"]) { + const buffer: Buffer = Buffer.from("dGhpcyBpcyBhIHTDqXN0", encoding); + assertEquals(buffer.length, 15, "Buffer length should be 15"); + assertEquals( + buffer.toString(), + "this is a tést", + "Buffer to string should recover the string" + ); + } + }, +}); + +Deno.test({ + name: "Buffer to string base64", + fn() { + for (const encoding of ["base64", "BASE64"]) { + const buffer: Buffer = Buffer.from("deno land"); + assertEquals( + buffer.toString(encoding), + "ZGVubyBsYW5k", + "Buffer to string should recover the string in base64" + ); + } + const b64 = "dGhpcyBpcyBhIHTDqXN0"; + assertEquals(Buffer.from(b64, "base64").toString("base64"), b64); + }, +}); + +Deno.test({ + name: "Buffer to string hex", + fn() { + for (const encoding of ["hex", "HEX"]) { + const buffer: Buffer = Buffer.from("deno land"); + assertEquals( + buffer.toString(encoding), + "64656e6f206c616e64", + "Buffer to string should recover the string" + ); + } + const hex = "64656e6f206c616e64"; + assertEquals(Buffer.from(hex, "hex").toString("hex"), hex); + }, +}); + +Deno.test({ + name: "Buffer to string invalid encoding", + fn() { + const buffer: Buffer = Buffer.from("deno land"); + const invalidEncodings = [null, 5, {}, true, false, "foo", ""]; + + for (const encoding of invalidEncodings) { + assertThrows( + () => { + // deno-lint-ignore ban-ts-comment + // @ts-ignore + buffer.toString(encoding); + }, + TypeError, + `Unkown encoding: ${encoding}`, + "Should throw on invalid encoding" + ); + } + }, +}); + +Deno.test({ + name: "Buffer from string invalid encoding", + fn() { + const defaultToUtf8Encodings = [null, 5, {}, true, false, ""]; + const invalidEncodings = ["deno", "base645"]; + + for (const encoding of defaultToUtf8Encodings) { + // deno-lint-ignore ban-ts-comment + // @ts-ignore + assertEquals(Buffer.from("yes", encoding).toString(), "yes"); + } + + for (const encoding of invalidEncodings) { + assertThrows( + () => { + // deno-lint-ignore ban-ts-comment + // @ts-ignore + Buffer.from("yes", encoding); + }, + TypeError, + `Unkown encoding: ${encoding}` + ); + } + }, +}); + +Deno.test({ + name: "Buffer to/from string not implemented encodings", + fn() { + const buffer: Buffer = Buffer.from("deno land"); + const notImplemented = ["ascii", "binary"]; + + for (const encoding of notImplemented) { + assertThrows( + () => { + // deno-lint-ignore ban-ts-comment + // @ts-ignore + buffer.toString(encoding); + }, + Error, + `"${encoding}" encoding`, + "Should throw on invalid encoding" + ); + + assertThrows( + () => { + // deno-lint-ignore ban-ts-comment + // @ts-ignore + Buffer.from("", encoding); + }, + Error, + `"${encoding}" encoding`, + "Should throw on invalid encoding" + ); + } + }, +}); + Deno.test({ name: "Buffer from another buffer creates a Buffer", fn() {