diff --git a/src/buffer.ts b/src/buffer.ts index 791de8e..238d10c 100644 --- a/src/buffer.ts +++ b/src/buffer.ts @@ -3,6 +3,22 @@ import type { Buffer as NodeBuffer } from 'buffer'; export class Buffer extends Uint8Array implements NodeBuffer { protected view = new DataView(this.buffer, this.byteOffset, this.byteLength); + /* constructor(str: string, encoding?: BufferEncoding); + constructor(size: number); + constructor(array: Uint8Array | ArrayBufferLike | readonly any[]); + constructor(dataOrLength: readonly any[] | string | number | Uint8Array | ArrayBufferLike, encoding?: BufferEncoding) */ + + constructor(input: string | ArrayBufferLike | Uint8Array | number, encoding?: BufferEncoding) { + if (typeof input === 'number') { + super(input); + } else if (typeof input === 'string') { + super(encode(input, encoding || 'utf8')); + } else { + super(input); + } + this.view = new DataView(this.buffer, this.byteOffset, this.byteLength); + } + write(string: string, offset?: number | BufferEncoding, length?: number | BufferEncoding, encoding?: BufferEncoding): number { let actualOffset = 0; let actualLength = this.length; @@ -383,10 +399,180 @@ export class Buffer extends Uint8Array implements NodeBuffer { this.view.setFloat64(offset, value, false); return offset + 8; } + + /* from(arrayBuffer: WithImplicitCoercion, byteOffset?: number, length?: number): Buffer; + from(data: WithImplicitCoercion): Buffer; + from(str: WithImplicitCoercion | { [Symbol.toPrimitive](hint: 'string'): string }, encoding?: BufferEncoding): Buffer; */ + from( + value: WithImplicitCoercion | { [Symbol.toPrimitive](hint: 'string'): string }, + encoding?: number | BufferEncoding, + length?: number + ): Buffer { + if (typeof value === 'string') return new Buffer(encode(value, encoding || 'utf8')); + if (value instanceof ArrayBuffer || value instanceof SharedArrayBuffer) return new Buffer(new Uint8Array(value)); + if (Array.isArray(value)) return new Buffer(new Uint8Array(value)); + return new Buffer(value); + } + + // alloc(size: number, fill?: string | Uint8Array | number, encoding?: BufferEncoding): Buffer + static alloc(size: number, fill?: string | number | Uint8Array, encoding: BufferEncoding = 'utf8'): Buffer { + const buffer = new Buffer(size); + if (fill === undefined) return buffer; + + if (typeof fill === 'string') { + buffer.fill(encode(fill, encoding)); + } else if (typeof fill === 'number') { + buffer.fill(fill); + } else { + buffer.set(fill); + } + + return buffer; + } + + // allocUnsafe(size: number): Buffer + static allocUnsafe(size: number): Buffer { + return new Buffer(size); + } + + // concat(list: readonly Uint8Array[], totalLength?: number): Buffer + static concat(list: Buffer[], totalLength?: number): Buffer { + if (list.length === 0) return Buffer.alloc(0); + + totalLength ??= list.reduce((acc, buf) => acc + buf.length, 0); + + const buffer = Buffer.allocUnsafe(totalLength); + let offset = 0; + for (const buf of list) { + buffer.set(buf, offset); + offset += buf.length; + } + return buffer; + } + + static isBuffer(obj: any): obj is Buffer { + return obj instanceof Buffer; + } + + // byteLength(string: string | Buffer | NodeJS.ArrayBufferView | ArrayBufferLike, encoding?: BufferEncoding): number {} + static byteLength(string: string | ArrayBufferLike | NodeJS.ArrayBufferView, encoding: BufferEncoding = 'utf8'): number { + if (typeof string === 'string') { + return encode(string, encoding).length; + } else if (string instanceof ArrayBuffer || string instanceof SharedArrayBuffer || string instanceof Uint8Array) { + return string.byteLength; + } else { + throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer.'); + } + } + + static isEncoding(encoding: string): encoding is BufferEncoding { + return ['utf8', 'utf-8', 'ascii', 'latin1', 'binary', 'base64', 'base64url', 'hex', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le'].includes(encoding.toLowerCase()); + } + + static compare(buf1: Uint8Array, buf2: Uint8Array): -1 | 0 | 1 { + if (!(buf1 instanceof Uint8Array) || !(buf2 instanceof Uint8Array)) { + throw new TypeError('Arguments must be Buffers'); + } + if (buf1 === buf2) return 0; + + for (let i = 0; i < Math.min(buf1.length, buf2.length); i++) { + if (buf1[i] !== buf2[i]) { + return buf1[i] < buf2[i] ? -1 : 1; + } + } + if (buf1.length < buf2.length) return -1; + if (buf1.length > buf2.length) return 1; + return 0; + } + + static of(...items: number[]): Buffer {} + + static copyBytesFrom(view: NodeJS.TypedArray, offset?: number, length?: number): Buffer {} + + static allocUnsafeSlow(size: number): Buffer {} + + static poolSize: number; } +Buffer satisfies typeof NodeBuffer; + function decode(data: Uint8Array, encoding: BufferEncoding) { - if (encoding == 'utf16le') encoding = 'utf-16le'; - const decoder = new TextDecoder(encoding); - return decoder.decode(data); + switch (encoding) { + case 'base64': + return btoa(String.fromCharCode(...data)); + case 'base64url': + return btoa(String.fromCharCode(...data)) + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, ''); + case 'binary': + return String.fromCharCode(...data); + case 'hex': + return Array.from(data) + .map(byte => byte.toString(16).padStart(2, '0')) + .join(''); + case 'utf16le': + case 'ucs2': + case 'ucs-2': + encoding = 'utf-16le'; + // Fall through to use TextDecoder + default: + return new TextDecoder(encoding).decode(data); + } +} + +function encode(input: string, encoding: BufferEncoding): Uint8Array { + switch (encoding) { + case 'utf-16le': + case 'utf16le': + case 'ucs2': + case 'ucs-2': { + const buffer = new Uint8Array(input.length * 2); + for (let i = 0; i < input.length; i++) { + const code = input.charCodeAt(i); + buffer[i * 2] = code & 0xff; + buffer[i * 2 + 1] = code >> 8; + } + return buffer; + } + case 'base64': { + const binaryString = atob(input); + const buffer = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + buffer[i] = binaryString.charCodeAt(i); + } + return buffer; + } + case 'base64url': { + input = input.replace(/-/g, '+').replace(/_/g, '/'); + while (input.length % 4 !== 0) { + input += '='; + } + const binaryString = atob(input); + const buffer = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + buffer[i] = binaryString.charCodeAt(i); + } + return buffer; + } + case 'binary': { + const buffer = new Uint8Array(input.length); + for (let i = 0; i < input.length; i++) { + buffer[i] = input.charCodeAt(i); + } + return buffer; + } + case 'hex': { + const buffer = new Uint8Array(input.length / 2); + for (let i = 0; i < buffer.length; i++) { + buffer[i] = parseInt(input.substr(i * 2, 2), 16); + } + return buffer; + } + case 'ascii': + case 'utf8': + case 'utf-8': + case 'latin1': + return new TextEncoder().encode(input); + } }