// node:buffer. Buffer extends Uint8Array, so every byte path that takes a Uint8Array // takes a Buffer. utf8 rides TextEncoder/TextDecoder; base64, base64url and hex ride // Uint8Array's fromBase64/toBase64/fromHex/toHex where the engine has them, with // atob/btoa and byte loops as the fallback; latin1, ascii and utf16le are byte loops // (TextDecoder's latin1 is windows-1252, not Node's latin1). import { invalidArgType } from "./errors"; export type Encoding = "utf8" | "hex" | "base64" | "base64url" | "latin1" | "ascii" | "utf16le"; const ALIASES: Record = { utf8: "utf8", "utf-8": "utf8", hex: "hex", base64: "base64", base64url: "base64url", latin1: "latin1", binary: "latin1", ascii: "ascii", utf16le: "utf16le", "utf-16le": "utf16le", ucs2: "utf16le", "ucs-2": "utf16le", }; function normalize(encoding: unknown): Encoding { if (encoding === undefined || encoding === null) return "utf8"; const hit = ALIASES[String(encoding).toLowerCase()]; if (!hit) throw Object.assign(new TypeError(`Unknown encoding: ${String(encoding)}`), { code: "ERR_UNKNOWN_ENCODING" }); return hit; } const utf8Encoder = new TextEncoder(); const utf8Decoder = new TextDecoder(); type Alphabet = "base64" | "base64url"; interface NativeStatics { fromBase64(s: string, o: { alphabet: Alphabet; lastChunkHandling: "loose" }): Uint8Array; fromHex(s: string): Uint8Array; } interface NativeMethods { toBase64(o: { alphabet: Alphabet; omitPadding: boolean }): string; toHex(): string; } const U8 = Uint8Array as unknown as Partial; const native = typeof U8.fromBase64 === "function" && typeof U8.fromHex === "function" && "toBase64" in Uint8Array.prototype && "toHex" in Uint8Array.prototype; const statics = U8 as NativeStatics; const methods = (bytes: Uint8Array): NativeMethods => bytes as unknown as NativeMethods; /** The native decoders are strict where Node is lenient: they throw on a character * outside one alphabet, data after padding, a dangling sextet, an odd hex length or a * bad hex pair. Whenever they accept, the answer is Node's, so a throw means "take * the lenient path". An odd hex length is trimmed first: Node drops the last nibble. */ function nativeDecode(str: string, enc: "hex" | Alphabet): Uint8Array | null { if (!native) return null; try { if (enc === "hex") return statics.fromHex(str.length % 2 ? str.slice(0, -1) : str); return statics.fromBase64(str, { alphabet: /[-_]/.test(str) ? "base64url" : "base64", lastChunkHandling: "loose" }); } catch { return null; } } const HEX_PAIR = /^[0-9a-fA-F]{2}$/; function encode(str: string, enc: Encoding): Uint8Array { switch (enc) { case "utf8": return utf8Encoder.encode(str); case "hex": { const fast = nativeDecode(str, enc); if (fast) return fast; const n = str.length >>> 1; const out = new Uint8Array(n); let i = 0; for (; i < n; i++) { const pair = str.slice(i * 2, i * 2 + 2); if (!HEX_PAIR.test(pair)) break; out[i] = Number.parseInt(pair, 16); } return out.subarray(0, i); } case "base64": case "base64url": { const fast = nativeDecode(str, enc); if (fast) return fast; // Node stops at the first '=', skips characters outside both alphabets, and // drops a dangling sextet. let clean = str.split("=")[0]!.replace(/[^A-Za-z0-9+/\-_]/g, "").replace(/-/g, "+").replace(/_/g, "/"); if (clean.length % 4 === 1) clean = clean.slice(0, -1); const bin = atob(clean + "=".repeat((4 - (clean.length % 4)) % 4)); const out = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i); return out; } case "latin1": case "ascii": { const out = new Uint8Array(str.length); for (let i = 0; i < str.length; i++) out[i] = str.charCodeAt(i) & 0xff; return out; } case "utf16le": { const out = new Uint8Array(str.length * 2); for (let i = 0; i < str.length; i++) { const c = str.charCodeAt(i); out[i * 2] = c & 0xff; out[i * 2 + 1] = c >>> 8; } return out; } } } function decode(bytes: Uint8Array, enc: Encoding): string { switch (enc) { case "utf8": return utf8Decoder.decode(bytes); case "hex": { if (native) return methods(bytes).toHex(); let s = ""; for (const b of bytes) s += b.toString(16).padStart(2, "0"); return s; } case "base64": case "base64url": { if (native) return methods(bytes).toBase64({ alphabet: enc, omitPadding: enc === "base64url" }); let bin = ""; for (let i = 0; i < bytes.length; i += 0x8000) bin += String.fromCharCode(...bytes.subarray(i, i + 0x8000)); const b64 = btoa(bin); return enc === "base64" ? b64 : b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } case "latin1": { let s = ""; for (let i = 0; i < bytes.length; i += 0x8000) s += String.fromCharCode(...bytes.subarray(i, i + 0x8000)); return s; } case "ascii": { let s = ""; for (const b of bytes) s += String.fromCharCode(b & 0x7f); return s; } case "utf16le": { let s = ""; for (let i = 0; i + 1 < bytes.length; i += 2) s += String.fromCharCode(bytes[i]! | (bytes[i + 1]! << 8)); return s; } } } function invalidArg(name: string, value: unknown): TypeError { return Object.assign( new TypeError(`The "${name}" argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received ${value === null ? "null" : typeof value}`), { code: "ERR_INVALID_ARG_TYPE" }, ); } export class Buffer extends Uint8Array { static readonly poolSize = 8192; /** A Buffer view over `bytes` without copying. */ static #view(bytes: Uint8Array): Buffer { return new Buffer(bytes.buffer as ArrayBuffer, bytes.byteOffset, bytes.byteLength); } static from(value: unknown, encodingOrOffset?: unknown, length?: number): Buffer { if (typeof value === "string") return Buffer.#view(encode(value, normalize(encodingOrOffset))); if (value instanceof ArrayBuffer || (typeof SharedArrayBuffer !== "undefined" && value instanceof SharedArrayBuffer)) { const offset = Number(encodingOrOffset ?? 0); return new Buffer(value as ArrayBuffer, offset, length ?? value.byteLength - offset); } if (ArrayBuffer.isView(value)) { const src = new Uint8Array(value.buffer, value.byteOffset, value.byteLength); const out = Buffer.allocUnsafe(src.length); out.set(src); return out; } if (Array.isArray(value) || (value !== null && typeof value === "object" && typeof (value as { length?: unknown }).length === "number")) { const arr = value as ArrayLike; const out = Buffer.allocUnsafe(arr.length); for (let i = 0; i < arr.length; i++) out[i] = Number(arr[i]) & 0xff; return out; } if (value !== null && typeof value === "object" && (value as { type?: unknown }).type === "Buffer" && Array.isArray((value as { data?: unknown }).data)) { return Buffer.from((value as { data: number[] }).data); } throw invalidArg("first", value); } static alloc(size: number, fill?: string | number | Uint8Array, encoding?: string): Buffer { const buf = new Buffer(size); if (fill !== undefined && fill !== 0) buf.fill(fill, 0, size, encoding); return buf; } static allocUnsafe(size: number): Buffer { return new Buffer(size); } static allocUnsafeSlow(size: number): Buffer { return new Buffer(size); } static isBuffer(value: unknown): value is Buffer { return value instanceof Buffer; } static isEncoding(encoding: unknown): boolean { return typeof encoding === "string" && Object.hasOwn(ALIASES, encoding.toLowerCase()); } static byteLength(value: string | ArrayBufferView | ArrayBuffer, encoding?: string): number { if (typeof value === "string") { const enc = normalize(encoding); // Node estimates base64 and hex from the length instead of decoding. if (enc === "hex") return value.length >>> 1; if (enc === "base64" || enc === "base64url") { let n = value.length; if (value.charCodeAt(n - 1) === 0x3d) n--; if (n > 1 && value.charCodeAt(n - 1) === 0x3d) n--; return (n * 3) >>> 2; } return enc === "utf8" ? utf8Encoder.encode(value).length : encode(value, enc).length; } return value.byteLength; } static concat(list: readonly Uint8Array[], totalLength?: number): Buffer { const total = totalLength ?? list.reduce((n, b) => n + b.length, 0); const out = Buffer.alloc(total); let at = 0; for (const b of list) { if (at >= total) break; const take = b.subarray(0, total - at); out.set(take, at); at += take.length; } return out; } static compare(a: Uint8Array, b: Uint8Array): -1 | 0 | 1 { const n = Math.min(a.length, b.length); for (let i = 0; i < n; i++) { if (a[i] !== b[i]) return a[i]! < b[i]! ? -1 : 1; } return a.length === b.length ? 0 : a.length < b.length ? -1 : 1; } override toString(encoding?: string, start = 0, end = this.length): string { return decode(this.subarray(Math.max(0, start), Math.min(this.length, end)), normalize(encoding)); } /** util.inspect shows at most INSPECT_MAX_BYTES (50) bytes in hex. */ [Symbol.for("nodejs.util.inspect.custom")](): string { const shown = Math.min(50, this.length); let str = decode(this.subarray(0, shown), "hex").replace(/(.{2})/g, "$1 ").trim(); const remaining = this.length - 50; if (remaining > 0) str += ` ... ${remaining} more byte${remaining > 1 ? "s" : ""}`; return `<${this.constructor.name} ${str}>`; } toJSON(): { type: "Buffer"; data: number[] } { return { type: "Buffer", data: Array.from(this) }; } equals(other: Uint8Array): boolean { return Buffer.compare(this, other) === 0; } compare(other: Uint8Array): -1 | 0 | 1 { return Buffer.compare(this, other); } /** Node's slice is a view, like subarray (not a copy, unlike Uint8Array#slice). */ override slice(start?: number, end?: number): Buffer { return this.subarray(start, end) as Buffer; } copy(target: Uint8Array, targetStart = 0, sourceStart = 0, sourceEnd = this.length): number { const src = this.subarray(sourceStart, Math.min(sourceEnd, sourceStart + (target.length - targetStart))); target.set(src, targetStart); return src.length; } write(str: string, offsetOrEncoding?: number | string, lengthOrEncoding?: number | string, encoding?: string): number { if (typeof offsetOrEncoding === "string") return this.write(str, 0, this.length, offsetOrEncoding); if (typeof lengthOrEncoding === "string") return this.write(str, offsetOrEncoding, undefined, lengthOrEncoding); const offset = offsetOrEncoding ?? 0; const length = lengthOrEncoding ?? this.length - offset; const bytes = encode(str, normalize(encoding)).subarray(0, Math.max(0, Math.min(length, this.length - offset))); this.set(bytes, offset); return bytes.length; } override fill(value: string | number | Uint8Array, start = 0, end = this.length, encoding?: string): this { if (typeof value === "number") return super.fill(value & 0xff, start, end); const pattern = typeof value === "string" ? encode(value, normalize(encoding)) : value; if (pattern.length === 0) return super.fill(0, start, end); for (let i = start; i < end; i++) this[i] = pattern[(i - start) % pattern.length]!; return this; } /** Reverses bytes in groups of `n`; Node rejects a length that isn't a multiple. */ #swap(n: number, label: string): this { if (this.length % n !== 0) { throw Object.assign(new RangeError(`Buffer size must be a multiple of ${n * 8}-bits`), { code: "ERR_INVALID_BUFFER_SIZE" }); } void label; for (let i = 0; i < this.length; i += n) { for (let a = 0, b = n - 1; a < b; a++, b--) { const tmp = this[i + a]!; this[i + a] = this[i + b]!; this[i + b] = tmp; } } return this; } swap16(): this { return this.#swap(2, "16"); } swap32(): this { return this.#swap(4, "32"); } swap64(): this { return this.#swap(8, "64"); } /** Like Buffer.from(array), but variadic (no single-array-argument ambiguity). */ static of(...items: number[]): Buffer { return Buffer.from(items); } /** Copies `view`'s raw bytes (not sharing the backing ArrayBuffer), `offset` and * `length` counted in `view`'s own elements, as Node's copyBytesFrom does. */ static copyBytesFrom(view: ArrayBufferView, offset = 0, length?: number): Buffer { if (!ArrayBuffer.isView(view)) throw invalidArgType("view", "an instance of TypedArray", view); const elementSize = "BYTES_PER_ELEMENT" in view ? (view as unknown as { BYTES_PER_ELEMENT: number }).BYTES_PER_ELEMENT : 1; const elements = view.byteLength / elementSize; const start = offset; const count = length ?? elements - start; const bytes = new Uint8Array(view.buffer, view.byteOffset + start * elementSize, count * elementSize); const out = Buffer.allocUnsafe(bytes.length); out.set(bytes); return out; } } // Node assigns Buffer's statics as plain enumerable properties, and packages copy // them with for-in (safer-buffer, which iconv-lite and body-parser load). for (const name of [ "poolSize", "from", "alloc", "allocUnsafe", "allocUnsafeSlow", "isBuffer", "compare", "isEncoding", "concat", "byteLength", "of", "copyBytesFrom", ]) { Object.defineProperty(Buffer, name, { enumerable: true }); } export const kMaxLength = 2 ** 32; export const constants = { MAX_LENGTH: kMaxLength, MAX_STRING_LENGTH: 2 ** 29 - 24 }; export const kStringMaxLength = constants.MAX_STRING_LENGTH; /** util.inspect's cap on bytes shown for a Buffer, mirrored in the inspect method above. */ export const INSPECT_MAX_BYTES = 50; // Re-exports: Blob and File are the same object in a worker as in Node (buffer.Blob // *is* the global Blob, as Node's own docs say). export const Blob = globalThis.Blob; export const File = globalThis.File; /** A private registry so resolveObjectURL can look blob: URLs up synchronously: the * browser's own object-URL table has no synchronous read, only fetch(). We keep our * own by wrapping URL.createObjectURL/revokeObjectURL, which every call (guest or * runtime) goes through since there is only one of each per realm. */ const blobUrls = new Map(); if (typeof URL !== "undefined" && typeof URL.createObjectURL === "function") { const nativeCreate = URL.createObjectURL.bind(URL); const nativeRevoke = URL.revokeObjectURL.bind(URL); URL.createObjectURL = (obj: Parameters[0]): string => { const url = nativeCreate(obj); if (obj instanceof Blob) blobUrls.set(url, obj); return url; }; URL.revokeObjectURL = (url: string): void => { blobUrls.delete(url); nativeRevoke(url); }; } /** buffer.resolveObjectURL: a synchronous lookup against the registry above, instead * of Node's internal one. Returns undefined for anything not registered here. Like * Node's own (which deserializes a stored handle into a fresh object), this returns * a copy, not the original Blob reference: slice() with no bounds but its own type, * so callers can't mutate the registry's copy through what they get back. */ export function resolveObjectURL(url: string): Blob | undefined { const blob = blobUrls.get(String(url)); return blob?.slice(0, blob.size, blob.type); } const TRANSCODE_ENCODINGS: Encoding[] = ["ascii", "utf8", "utf16le", "latin1"]; /** buffer.transcode: re-encode bytes from one encoding to another, both of which must * be among the four Node supports here (not base64 or hex, which Node also refuses * with this same ICU-flavoured error). */ export function transcode(source: Uint8Array, fromEncoding: string, toEncoding: string): Buffer { if (!(source instanceof Uint8Array)) throw invalidArgType("source", "an instance of Buffer or Uint8Array", source); const from = ALIASES[String(fromEncoding).toLowerCase()]; const to = ALIASES[String(toEncoding).toLowerCase()]; if (!from || !to || !TRANSCODE_ENCODINGS.includes(from) || !TRANSCODE_ENCODINGS.includes(to)) { throw Object.assign(new Error("Unable to transcode Buffer [U_ILLEGAL_ARGUMENT_ERROR]"), { code: "U_ILLEGAL_ARGUMENT_ERROR" }); } const bytes = encode(decode(source, from), to); return Buffer.from(bytes.buffer as ArrayBuffer, bytes.byteOffset, bytes.byteLength); } const strictUtf8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }); /** buffer.isUtf8: whether the bytes are valid UTF-8. */ export function isUtf8(input: unknown): boolean { if (!(input instanceof ArrayBuffer) && !ArrayBuffer.isView(input)) { throw Object.assign(new TypeError('The "input" argument must be an instance of ArrayBuffer, Buffer, or TypedArray.'), { code: "ERR_INVALID_ARG_TYPE" }); } try { strictUtf8.decode(input); return true; } catch { return false; } } /** buffer.isAscii: whether every byte is below 0x80. */ export function isAscii(input: unknown): boolean { if (!(input instanceof ArrayBuffer) && !ArrayBuffer.isView(input)) { throw invalidArgType("input", "an instance of ArrayBuffer, Buffer, or TypedArray", input); } const bytes = input instanceof ArrayBuffer ? new Uint8Array(input) : new Uint8Array(input.buffer, input.byteOffset, input.byteLength); for (const b of bytes) if (b > 0x7f) return false; return true; } // ---- numeric reads and writes (readUInt16BE, writeDoubleLE, readBigInt64LE, ...) ---- // Installed on Buffer.prototype from one table. Checks and messages follow Node's // lib/internal/buffer.js: the offset's type, then bounds (boundsError), and for writes // the value's range (checkInt) in the order each width checks them. function addNumericalSeparator(text: string): string { let res = ""; let i = text.length; const start = text[0] === "-" ? 1 : 0; for (; i >= start + 4; i -= 3) res = `_${text.slice(i - 3, i)}${res}`; return `${text.slice(0, i)}${res}`; } function outOfRange(name: string, range: string, received: number | bigint): RangeError { const shown = typeof received === "bigint" ? `${received > 2n ** 32n || received < -(2n ** 32n) ? addNumericalSeparator(String(received)) : String(received)}n` : Number.isInteger(received) && Math.abs(received) > 2 ** 32 ? addNumericalSeparator(String(received)) : String(received); return Object.assign(new RangeError(`The value of "${name}" is out of range. It must be ${range}. Received ${shown}`), { code: "ERR_OUT_OF_RANGE" }); } function validateNumber(value: unknown, name: string): asserts value is number { if (typeof value !== "number") throw invalidArgType(name, "of type number", value); } function boundsError(value: number, length: number, type?: string): never { if (Math.floor(value) !== value) { validateNumber(value, type ?? "offset"); throw outOfRange(type ?? "offset", "an integer", value); } if (length < 0) throw Object.assign(new RangeError("Attempt to access memory outside buffer bounds"), { code: "ERR_BUFFER_OUT_OF_BOUNDS" }); throw outOfRange(type ?? "offset", `>= ${type ? 1 : 0} and <= ${length}`, value); } /** The offset is a number naming `size` bytes inside the buffer. */ function checkBounds(buf: Uint8Array, offset: unknown, size: number): number { validateNumber(offset, "offset"); if (Math.floor(offset) !== offset || offset < 0 || offset + size > buf.length) boundsError(offset, buf.length - size); return offset; } /** Node's checkInt range text: exact bounds up to 32 bits, powers of two above. */ function checkInt(value: number | bigint, min: number | bigint, max: number | bigint, size: number): void { if (value <= max && value >= min) return; const n = typeof min === "bigint" ? "n" : ""; const bits = size * 8; const range = size <= 4 ? `>= ${min}${n} and <= ${max}${n}` : min === 0 || min === 0n ? `>= 0${n} and < 2${n} ** ${bits}${n}` : `>= -(2${n} ** ${bits - 1}${n}) and < 2 ** ${bits - 1}${n}`; throw outOfRange("value", range, value); } const viewOf = (buf: Uint8Array): DataView => new DataView(buf.buffer, buf.byteOffset, buf.byteLength); type Width = { size: number; signed: boolean }; function readVariable(buf: Uint8Array, offset: number, { size, signed }: Width, le: boolean): number { checkBounds(buf, offset, size); let value = 0; for (let i = 0; i < size; i++) value = value * 256 + buf[offset + (le ? size - 1 - i : i)]!; return signed && value >= 2 ** (size * 8 - 1) ? value - 2 ** (size * 8) : value; } function writeVariable(buf: Uint8Array, value: unknown, offset: unknown, { size, signed }: Width, le: boolean): number { const v = +(value as number); const bits = size * 8; const [min, max] = signed ? [-(2 ** (bits - 1)), 2 ** (bits - 1) - 1] : [0, 2 ** bits - 1]; // An 8-bit write checks the offset's type before the value; wider writes after. if (size === 1) validateNumber(offset, "offset"); checkInt(v, min, max, size); const at = checkBounds(buf, offset, size); let rest = Math.trunc(v); if (rest < 0) rest += 2 ** bits; for (let i = size - 1; i >= 0; i--) { buf[at + (le ? size - 1 - i : i)] = rest % 256; rest = Math.floor(rest / 256); } return at + size; } const FLOATS = [ { name: "Float", size: 4, get: DataView.prototype.getFloat32, set: DataView.prototype.setFloat32 }, { name: "Double", size: 8, get: DataView.prototype.getFloat64, set: DataView.prototype.setFloat64 }, ] as const; const BIGINTS = [ { name: "BigUInt64", alias: "BigUint64", min: 0n, max: 2n ** 64n - 1n, get: DataView.prototype.getBigUint64, set: DataView.prototype.setBigUint64 }, { name: "BigInt64", alias: null, min: -(2n ** 63n), max: 2n ** 63n - 1n, get: DataView.prototype.getBigInt64, set: DataView.prototype.setBigInt64 }, ] as const; function installNumberMethods(proto: object): void { const define = (name: string, fn: (this: Uint8Array, ...args: any[]) => unknown, alias?: string): void => { for (const key of alias ? [name, alias] : [name]) Object.defineProperty(proto, key, { value: fn, writable: true, configurable: true }); }; for (const size of [1, 2, 4]) { for (const signed of [false, true]) { const width = { size, signed }; const base = `${signed ? "Int" : "UInt"}${size * 8}`; const alias = signed ? undefined : `Uint${size * 8}`; for (const order of size === 1 ? [""] : ["BE", "LE"]) { const le = order === "LE"; define(`read${base}${order}`, function (offset = 0) { return readVariable(this, offset, width, le); }, alias && `read${alias}${order}`); define(`write${base}${order}`, function (value: unknown, offset = 0) { return writeVariable(this, value, offset, width, le); }, alias && `write${alias}${order}`); } } } for (const signed of [false, true]) { for (const order of ["BE", "LE"]) { const le = order === "LE"; const base = signed ? "Int" : "UInt"; define(`read${base}${order}`, function (offset: unknown, byteLength: unknown) { if (offset === undefined) throw invalidArgType("offset", "of type number", offset); if (typeof byteLength !== "number" || byteLength < 1 || byteLength > 6 || Math.floor(byteLength) !== byteLength) boundsError(byteLength as number, 6, "byteLength"); return readVariable(this, offset as number, { size: byteLength, signed }, le); }, signed ? undefined : `readUint${order}`); define(`write${base}${order}`, function (value: unknown, offset: unknown, byteLength: unknown) { if (typeof byteLength !== "number" || byteLength < 1 || byteLength > 6 || Math.floor(byteLength) !== byteLength) boundsError(byteLength as number, 6, "byteLength"); return writeVariable(this, value, offset, { size: byteLength, signed }, le); }, signed ? undefined : `writeUint${order}`); } } for (const f of FLOATS) { for (const order of ["BE", "LE"]) { const le = order === "LE"; define(`read${f.name}${order}`, function (offset = 0) { return f.get.call(viewOf(this), checkBounds(this, offset, f.size), le); }); define(`write${f.name}${order}`, function (value: unknown, offset = 0) { const at = checkBounds(this, offset, f.size); f.set.call(viewOf(this), at, +(value as number), le); return at + f.size; }); } } for (const b of BIGINTS) { for (const order of ["BE", "LE"]) { const le = order === "LE"; define(`read${b.name}${order}`, function (offset = 0) { return b.get.call(viewOf(this), checkBounds(this, offset, 8), le); }, b.alias ? `read${b.alias}${order}` : undefined); define(`write${b.name}${order}`, function (value: bigint, offset = 0) { checkInt(value, b.min, b.max, 8); const at = checkBounds(this, offset, 8); // Node splits the value with BigInt arithmetic, which is what rejects a number. void (value & 0xffffffffn); b.set.call(viewOf(this), at, value, le); return at + 8; }, b.alias ? `write${b.alias}${order}` : undefined); } } } installNumberMethods(Buffer.prototype);