From 0374eadcf7ecb054dae1b1587843a2006fdf4c2d Mon Sep 17 00:00:00 2001 From: Marcos Casagrande Date: Sun, 28 Jun 2020 18:27:02 +0200 Subject: [PATCH] feat(std/node): Add Buffer.isEncoding (#6521) --- std/node/_utils.ts | 64 +++++++++++++++++++++++++++++++++++++++++ std/node/buffer.ts | 26 ++++++++++------- std/node/buffer_test.ts | 47 ++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 11 deletions(-) diff --git a/std/node/_utils.ts b/std/node/_utils.ts index 463d4e67ca..66b7b10d9b 100644 --- a/std/node/_utils.ts +++ b/std/node/_utils.ts @@ -44,3 +44,67 @@ export function spliceOne(list: string[], index: number): void { for (; index + 1 < list.length; index++) list[index] = list[index + 1]; list.pop(); } + +// Taken from: https://github.com/nodejs/node/blob/ba684805b6c0eded76e5cd89ee00328ac7a59365/lib/internal/util.js#L125 +// Return undefined if there is no match. +// Move the "slow cases" to a separate function to make sure this function gets +// inlined properly. That prioritizes the common case. +export function normalizeEncoding(enc: string | null): string | undefined { + if (enc == null || enc === "utf8" || enc === "utf-8") return "utf8"; + return slowCases(enc); +} + +// https://github.com/nodejs/node/blob/ba684805b6c0eded76e5cd89ee00328ac7a59365/lib/internal/util.js#L130 +function slowCases(enc: string): string | undefined { + switch (enc.length) { + case 4: + if (enc === "UTF8") return "utf8"; + if (enc === "ucs2" || enc === "UCS2") return "utf16le"; + enc = `${enc}`.toLowerCase(); + if (enc === "utf8") return "utf8"; + if (enc === "ucs2") return "utf16le"; + break; + case 3: + if (enc === "hex" || enc === "HEX" || `${enc}`.toLowerCase() === "hex") + return "hex"; + break; + case 5: + if (enc === "ascii") return "ascii"; + if (enc === "ucs-2") return "utf16le"; + if (enc === "UTF-8") return "utf8"; + if (enc === "ASCII") return "ascii"; + if (enc === "UCS-2") return "utf16le"; + enc = `${enc}`.toLowerCase(); + if (enc === "utf-8") return "utf8"; + if (enc === "ascii") return "ascii"; + if (enc === "ucs-2") return "utf16le"; + break; + case 6: + if (enc === "base64") return "base64"; + if (enc === "latin1" || enc === "binary") return "latin1"; + if (enc === "BASE64") return "base64"; + if (enc === "LATIN1" || enc === "BINARY") return "latin1"; + enc = `${enc}`.toLowerCase(); + if (enc === "base64") return "base64"; + if (enc === "latin1" || enc === "binary") return "latin1"; + break; + case 7: + if ( + enc === "utf16le" || + enc === "UTF16LE" || + `${enc}`.toLowerCase() === "utf16le" + ) + return "utf16le"; + break; + case 8: + if ( + enc === "utf-16le" || + enc === "UTF-16LE" || + `${enc}`.toLowerCase() === "utf-16le" + ) + return "utf16le"; + break; + default: + if (enc === "") return "utf8"; + } +} diff --git a/std/node/buffer.ts b/std/node/buffer.ts index b878092dcb..9ec7c04ffb 100644 --- a/std/node/buffer.ts +++ b/std/node/buffer.ts @@ -1,8 +1,7 @@ import * as hex from "../encoding/hex.ts"; import * as base64 from "../encoding/base64.ts"; -import { notImplemented } from "./_utils.ts"; +import { notImplemented, normalizeEncoding } from "./_utils.ts"; -const validEncodings = ["utf8", "hex", "base64"]; const notImplementedEncodings = [ "utf16le", "latin1", @@ -17,20 +16,16 @@ function checkEncoding(encoding = "utf8", strict = true): string { throw new TypeError(`Unkown encoding: ${encoding}`); } - encoding = encoding.toLowerCase(); - if (encoding === "utf-8" || encoding === "") { - return "utf8"; - } + const normalized = normalizeEncoding(encoding); + + if (normalized === undefined) + throw new TypeError(`Unkown encoding: ${encoding}`); if (notImplementedEncodings.includes(encoding)) { notImplemented(`"${encoding}" encoding`); } - if (!validEncodings.includes(encoding)) { - throw new TypeError(`Unkown encoding: ${encoding}`); - } - - return encoding; + return normalized; } /** @@ -181,6 +176,15 @@ export default class Buffer extends Uint8Array { return obj instanceof Buffer; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + static isEncoding(encoding: any): boolean { + return ( + typeof encoding === "string" && + encoding.length !== 0 && + normalizeEncoding(encoding) !== undefined + ); + } + /** * Copies data from a region of buf to a region in target, even if the target * memory region overlaps with buf. diff --git a/std/node/buffer_test.ts b/std/node/buffer_test.ts index 105bc284e4..4e6dc9afc5 100644 --- a/std/node/buffer_test.ts +++ b/std/node/buffer_test.ts @@ -471,3 +471,50 @@ Deno.test({ assertEquals(slice.toString(), "deno"); }, }); + +Deno.test({ + name: "isEncoding returns true for valid encodings", + fn() { + [ + "hex", + "HEX", + "HeX", + "utf8", + "utf-8", + "ascii", + "latin1", + "binary", + "base64", + "BASE64", + "BASe64", + "ucs2", + "ucs-2", + "utf16le", + "utf-16le", + ].forEach((enc) => { + assertEquals(Buffer.isEncoding(enc), true); + }); + }, +}); + +Deno.test({ + name: "isEncoding returns false for invalid encodings", + fn() { + [ + "utf9", + "utf-7", + "Unicode-FTW", + "new gnu gun", + false, + NaN, + {}, + Infinity, + [], + 1, + 0, + -1, + ].forEach((enc) => { + assertEquals(Buffer.isEncoding(enc), false); + }); + }, +});