import { Buffer } from 'buffer';
import { normalizeEncoding } from '__wasm_rquickjs_builtin/internal/normalize_encoding';
import {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_THIS,
ERR_UNKNOWN_ENCODING,
} from '__wasm_rquickjs_builtin/internal/errors';
import { utf8_decode as nativeUtf8Decode } from '__wasm_rquickjs_builtin/string_decoder_native';
const kDecoder = Symbol('StringDecoder');
function coerceToBuffer(buf) {
if (Buffer.isBuffer(buf)) return buf;
if (ArrayBuffer.isView(buf))
return Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength);
throw new ERR_INVALID_ARG_TYPE(
'buf',
['Buffer', 'TypedArray', 'DataView'],
buf
);
}
function checkThis(self) {
if (!self || !self[kDecoder])
throw new ERR_INVALID_THIS('StringDecoder');
}
function utf8CheckByte(byte) {
if (byte <= 0x7f) return 0;
if (byte >> 5 === 0x06) return 2;
if (byte >> 4 === 0x0e) return 3;
if (byte >> 3 === 0x1e) return 4;
return byte >> 6 === 0x02 ? -1 : -2;
}
function utf8CheckIncomplete(self, buf, i) {
let j = buf.length - 1;
if (j < i) return 0;
let nb = utf8CheckByte(buf[j]);
if (nb >= 0) {
if (nb > 0) self.lastNeed = nb - 1;
return nb;
}
if (--j < i || nb === -2) return 0;
nb = utf8CheckByte(buf[j]);
if (nb >= 0) {
if (nb > 0) self.lastNeed = nb - 2;
return nb;
}
if (--j < i || nb === -2) return 0;
nb = utf8CheckByte(buf[j]);
if (nb >= 0) {
if (nb > 0) {
if (nb === 2) nb = 0;
else self.lastNeed = nb - 3;
}
return nb;
}
return 0;
}
function utf8FillLast(buf) {
const p = this.lastTotal - this.lastNeed;
let consumed = 0;
while (consumed < this.lastNeed && consumed < buf.length) {
if ((buf[consumed] & 0xc0) !== 0x80) break;
consumed++;
}
const broken = consumed < this.lastNeed && consumed < buf.length;
if (broken) {
const totalBytes = p + consumed;
const tmp = new Uint8Array(totalBytes);
for (let k = 0; k < p; k++) tmp[k] = this.lastChar[k];
for (let k = 0; k < consumed; k++) tmp[p + k] = buf[k];
const r = nativeUtf8Decode(tmp, 0, totalBytes);
this.lastNeed = consumed; return r;
}
if (this.lastNeed <= buf.length) {
buf.copy(this.lastChar, p, 0, this.lastNeed);
return utf8Decode(this.lastChar, 0, this.lastTotal);
}
buf.copy(this.lastChar, p, 0, buf.length);
this.lastNeed -= buf.length;
}
function utf8Decode(buf, start, end) {
return nativeUtf8Decode(buf, start, end);
}
function utf8Text(buf, i) {
const total = utf8CheckIncomplete(this, buf, i);
if (!this.lastNeed) return utf8Decode(buf, i, buf.length);
this.lastTotal = total;
const end = buf.length - (total - this.lastNeed);
buf.copy(this.lastChar, 0, end);
return utf8Decode(buf, i, end);
}
function utf8End(buf) {
let r = buf && buf.length ? this.write(buf) : '';
if (this.lastNeed) {
r += '\ufffd';
this.lastNeed = 0;
this.lastTotal = 0;
}
return r;
}
function utf16Text(buf, i) {
if ((buf.length - i) % 2 === 0) {
const r = buf.toString('utf16le', i);
if (r) {
const c = r.charCodeAt(r.length - 1);
if (c >= 0xd800 && c <= 0xdbff) {
this.lastNeed = 2;
this.lastTotal = 4;
this.lastChar[0] = buf[buf.length - 2];
this.lastChar[1] = buf[buf.length - 1];
return r.slice(0, -1);
}
}
return r;
}
this.lastNeed = 1;
this.lastTotal = 2;
this.lastChar[0] = buf[buf.length - 1];
return buf.toString('utf16le', i, buf.length - 1);
}
function utf16End(buf) {
let r = buf && buf.length ? this.write(buf) : '';
if (this.lastNeed) {
const end = this.lastTotal - this.lastNeed;
r += this.lastChar.toString('utf16le', 0, end);
this.lastNeed = 0;
this.lastTotal = 0;
}
return r;
}
function base64Text(buf, i) {
const n = (buf.length - i) % 3;
if (n === 0) return buf.toString(this.encoding, i);
this.lastNeed = 3 - n;
this.lastTotal = 3;
if (n === 1) {
this.lastChar[0] = buf[buf.length - 1];
} else {
this.lastChar[0] = buf[buf.length - 2];
this.lastChar[1] = buf[buf.length - 1];
}
return buf.toString(this.encoding, i, buf.length - n);
}
function base64End(buf) {
let r = buf && buf.length ? this.write(buf) : '';
if (this.lastNeed) {
r += this.lastChar.toString(this.encoding, 0, 3 - this.lastNeed);
this.lastNeed = 0;
this.lastTotal = 0;
}
return r;
}
function simpleWrite(buf) {
return buf.toString(this.encoding);
}
function simpleEnd(buf) {
return buf && buf.length ? this.write(buf) : '';
}
function fillLast(buf) {
if (this.lastNeed <= buf.length) {
buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed);
return this.lastChar.toString(this.encoding, 0, this.lastTotal);
}
buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length);
this.lastNeed -= buf.length;
}
export function StringDecoder(encoding) {
const enc = normalizeEncoding(encoding);
if (enc === undefined) {
throw new ERR_UNKNOWN_ENCODING(encoding);
}
this.encoding = enc;
this[kDecoder] = true;
let nb;
switch (enc) {
case 'utf16le':
this.text = utf16Text;
this.end = utf16End;
this.fillLast = fillLast;
nb = 4;
break;
case 'utf8':
this.fillLast = utf8FillLast;
nb = 4;
break;
case 'base64':
case 'base64url':
this.text = base64Text;
this.end = base64End;
this.fillLast = fillLast;
nb = 3;
break;
default:
this.write = simpleWrite;
this.end = simpleEnd;
return;
}
this.lastNeed = 0;
this.lastTotal = 0;
this.lastChar = Buffer.alloc(nb);
}
StringDecoder.prototype.write = function write(buf) {
checkThis(this);
if (typeof buf === 'string') return buf;
buf = coerceToBuffer(buf);
if (buf.length === 0) return '';
let r;
let i;
if (this.lastNeed) {
r = this.fillLast(buf);
if (r === undefined) return '';
i = this.lastNeed;
this.lastNeed = 0;
} else {
i = 0;
}
if (i < buf.length) return r ? r + this.text(buf, i) : this.text(buf, i);
return r || '';
};
StringDecoder.prototype.text = utf8Text;
StringDecoder.prototype.end = utf8End;
StringDecoder.prototype.fillLast = utf8FillLast;
export default StringDecoder;