diff --git a/lib/browser-buffer.js b/lib/browser-buffer.js new file mode 100644 index 0000000..9c61b56 --- /dev/null +++ b/lib/browser-buffer.js @@ -0,0 +1,206 @@ +"use strict" + +// Minimal Buffer shim for iconv-lite browser usage. +// Implements only the subset of Buffer API used by iconv-lite codecs. +// Extends Uint8Array so instanceof checks and standard web APIs work. + +var textEncoder = new TextEncoder() +var textDecoder = new TextDecoder() + +class BufferShim extends Uint8Array { + // Prevent inherited methods (like map, filter) from creating BufferShim instances. + static get [Symbol.species] () { return Uint8Array } + + static alloc (size, fill) { + var buf = new BufferShim(size) + if (fill !== undefined && fill !== 0) { + buf.fill(fill) + } + return buf + } + + static from (arg, encoding) { + if (typeof arg === "string") { + return BufferShim._fromString(arg, encoding) + } + // Uint8Array, Array, or other array-like + var result = new BufferShim(arg.length) + result.set(arg) + return result + } + + static _fromString (str, encoding) { + if (!encoding || encoding === "utf8" || encoding === "utf-8") { + var encoded = textEncoder.encode(str) + var buf = new BufferShim(encoded.length) + buf.set(encoded) + return buf + } + + if (encoding === "ucs2" || encoding === "ucs-2" || + encoding === "utf16le" || encoding === "utf-16le") { + var buf = new BufferShim(str.length * 2) + for (var i = 0; i < str.length; i++) { + var code = str.charCodeAt(i) + buf[i * 2] = code & 0xFF + buf[i * 2 + 1] = (code >> 8) & 0xFF + } + return buf + } + + if (encoding === "binary" || encoding === "latin1") { + var buf = new BufferShim(str.length) + for (var i = 0; i < str.length; i++) { + buf[i] = str.charCodeAt(i) & 0xFF + } + return buf + } + + if (encoding === "base64") { + // Node.js is lenient with base64 - strip invalid chars and add padding + var cleaned = str.replace(/[^A-Za-z0-9+/]/g, "") + while (cleaned.length % 4 !== 0) cleaned += "=" + var binaryStr + try { binaryStr = atob(cleaned) } catch (e) { binaryStr = "" } + var buf = new BufferShim(binaryStr.length) + for (var i = 0; i < binaryStr.length; i++) { + buf[i] = binaryStr.charCodeAt(i) + } + return buf + } + + if (encoding === "hex") { + var len = str.length >> 1 + var buf = new BufferShim(len) + for (var i = 0; i < len; i++) { + buf[i] = parseInt(str.substr(i * 2, 2), 16) + } + return buf + } + + if (encoding === "ascii") { + var buf = new BufferShim(str.length) + for (var i = 0; i < str.length; i++) { + buf[i] = str.charCodeAt(i) & 0x7F + } + return buf + } + + // Fallback to utf8 + return BufferShim._fromString(str, "utf8") + } + + static concat (list) { + var totalLength = 0 + for (var i = 0; i < list.length; i++) { + totalLength += list[i].length + } + var result = new BufferShim(totalLength) + var offset = 0 + for (var i = 0; i < list.length; i++) { + result.set(list[i], offset) + offset += list[i].length + } + return result + } + + static isBuffer (obj) { + return obj instanceof BufferShim + } + + toString (encoding) { + if (!encoding || encoding === "utf8" || encoding === "utf-8") { + return textDecoder.decode(this) + } + + if (encoding === "ucs2" || encoding === "ucs-2" || + encoding === "utf16le" || encoding === "utf-16le") { + var result = "" + var CHUNK = 8192 + var count = this.length >> 1 + for (var start = 0; start < count; start += CHUNK) { + var end = Math.min(start + CHUNK, count) + var codes = new Array(end - start) + for (var i = start; i < end; i++) { + codes[i - start] = this[i * 2] | (this[i * 2 + 1] << 8) + } + result += String.fromCharCode.apply(null, codes) + } + return result + } + + if (encoding === "base64") { + var binaryStr = "" + for (var i = 0; i < this.length; i++) { + binaryStr += String.fromCharCode(this[i]) + } + return btoa(binaryStr) + } + + if (encoding === "binary" || encoding === "latin1") { + var result = "" + for (var i = 0; i < this.length; i++) { + result += String.fromCharCode(this[i]) + } + return result + } + + if (encoding === "hex") { + var result = "" + for (var i = 0; i < this.length; i++) { + result += (this[i] < 16 ? "0" : "") + this[i].toString(16) + } + return result + } + + if (encoding === "ascii") { + var result = "" + for (var i = 0; i < this.length; i++) { + result += String.fromCharCode(this[i] & 0x7F) + } + return result + } + + // Fallback to utf8 + return textDecoder.decode(this) + } + + slice (start, end) { + var sliced = Uint8Array.prototype.slice.call(this, start, end) + Object.setPrototypeOf(sliced, BufferShim.prototype) + return sliced + } + + write (str, offset) { + // Only used in utf7.js for writing ASCII strings at an offset. + // Returns number of bytes written. + if (offset === undefined) offset = 0 + var len = Math.min(str.length, this.length - offset) + for (var i = 0; i < len; i++) { + this[offset + i] = str.charCodeAt(i) & 0xFF + } + return len + } + + readUInt16LE (offset) { + return this[offset] | (this[offset + 1] << 8) + } + + writeUInt32LE (value, offset) { + this[offset] = value & 0xFF + this[offset + 1] = (value >>> 8) & 0xFF + this[offset + 2] = (value >>> 16) & 0xFF + this[offset + 3] = (value >>> 24) & 0xFF + return offset + 4 + } + + writeUInt32BE (value, offset) { + this[offset] = (value >>> 24) & 0xFF + this[offset + 1] = (value >>> 16) & 0xFF + this[offset + 2] = (value >>> 8) & 0xFF + this[offset + 3] = value & 0xFF + return offset + 4 + } +} + +module.exports = { Buffer: BufferShim } diff --git a/lib/browser-string-decoder.js b/lib/browser-string-decoder.js new file mode 100644 index 0000000..719298c --- /dev/null +++ b/lib/browser-string-decoder.js @@ -0,0 +1,142 @@ +"use strict" + +// Minimal StringDecoder shim for iconv-lite browser usage. +// Replaces Node.js string_decoder module. +// Supports only the encodings used by iconv-lite's internal.js: utf8, ucs2, binary, base64, hex. + +function BrowserStringDecoder (encoding) { + this.encoding = normalizeEncoding(encoding) + + if (this.encoding === "utf8") { + this._decoder = new TextDecoder("utf-8", { fatal: false }) + } else if (this.encoding === "ucs2") { + this._lastByte = -1 + } else if (this.encoding === "base64") { + this._remainder = null + } + // binary, hex, and ascii need no state +} + +function normalizeEncoding (enc) { + enc = (enc || "utf8").toLowerCase().replace(/[^a-z0-9]/g, "") + if (enc === "utf8") return "utf8" + if (enc === "ucs2" || enc === "utf16le") return "ucs2" + if (enc === "binary" || enc === "latin1") return "binary" + if (enc === "base64") return "base64" + if (enc === "hex") return "hex" + if (enc === "ascii") return "ascii" + return "utf8" // fallback +} + +BrowserStringDecoder.prototype.write = function (buf) { + if (!buf || buf.length === 0) return "" + + switch (this.encoding) { + case "utf8": + return this._decoder.decode(buf, { stream: true }) + + case "ucs2": { + var str = "" + var i = 0 + if (this._lastByte !== -1) { + if (buf.length > 0) { + str += String.fromCharCode(this._lastByte | (buf[0] << 8)) + i = 1 + } + this._lastByte = -1 + } + var end = buf.length + if ((end - i) % 2 !== 0) { + this._lastByte = buf[end - 1] + end-- + } + for (; i < end; i += 2) { + str += String.fromCharCode(buf[i] | (buf[i + 1] << 8)) + } + return str + } + + case "binary": { + var str = "" + for (var i = 0; i < buf.length; i++) { + str += String.fromCharCode(buf[i]) + } + return str + } + + case "base64": { + var data = buf + if (this._remainder) { + var combined = new Uint8Array(this._remainder.length + buf.length) + combined.set(this._remainder) + combined.set(buf, this._remainder.length) + data = combined + this._remainder = null + } + var leftover = data.length % 3 + if (leftover > 0) { + this._remainder = data.slice(data.length - leftover) + data = data.slice(0, data.length - leftover) + } + if (data.length === 0) return "" + var binaryStr = "" + for (var i = 0; i < data.length; i++) { + binaryStr += String.fromCharCode(data[i]) + } + return btoa(binaryStr) + } + + case "hex": { + var str = "" + for (var i = 0; i < buf.length; i++) { + str += (buf[i] < 16 ? "0" : "") + buf[i].toString(16) + } + return str + } + + case "ascii": { + var str = "" + for (var i = 0; i < buf.length; i++) { + str += String.fromCharCode(buf[i] & 0x7F) + } + return str + } + + default: + return "" + } +} + +BrowserStringDecoder.prototype.end = function (buf) { + var res = "" + + if (buf && buf.length > 0) { + if (this.encoding === "utf8") { + // Final call without stream:true to flush + res = this._decoder.decode(buf) + } else { + res = this.write(buf) + } + } else if (this.encoding === "utf8") { + // Flush any remaining incomplete bytes + res = this._decoder.decode() + } + + // Flush buffered state + if (this.encoding === "ucs2" && this._lastByte !== -1) { + this._lastByte = -1 + } + + if (this.encoding === "base64" && this._remainder) { + var binaryStr = "" + for (var i = 0; i < this._remainder.length; i++) { + binaryStr += String.fromCharCode(this._remainder[i]) + } + res += btoa(binaryStr) + this._remainder = null + } + + return res +} + +module.exports = { StringDecoder: BrowserStringDecoder } diff --git a/package.json b/package.json index 2a57357..628c8b0 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,9 @@ "typegen": "node generation/gen-typings.js" }, "browser": { - "stream": false + "stream": false, + "safer-buffer": "./lib/browser-buffer.js", + "string_decoder": "./lib/browser-string-decoder.js" }, "devDependencies": { "@arethetypeswrong/cli": "^0.17.4",