#include #include #include #include #include "ant.h" #include "cfunc.h" #include "ptr.h" #include "utf8.h" #include "base64.h" #include "errors.h" #include "internal.h" #include "utils.h" #include "descriptors.h" #include "modules/buffer.h" #include "modules/textcodec.h" #include "modules/string_decoder.h" #define SD_ENC_UTF8 0 #define SD_ENC_UTF16LE 1 #define SD_ENC_UTF16BE 2 #define SD_ENC_LATIN1 3 #define SD_ENC_HEX 4 #define SD_ENC_BASE64 5 typedef struct { int encoding; td_state_t *td; uint8_t pending[2]; int pending_len; } sd_state_t; static constexpr uint32_t STRING_DECODER_NATIVE_TAG = Ant_NativeTag("SDEC"); static sd_state_t *sd_get_state(ant_value_t obj) { return (sd_state_t *)js_get_native(obj, STRING_DECODER_NATIVE_TAG); } static void sd_finalize(ant_t *js, ant_object_t *obj) { ant_value_t value = js_obj_from_ptr(obj); sd_state_t *st = (sd_state_t *)js_get_native(value, STRING_DECODER_NATIVE_TAG); if (st) { free(st->td); free(st); } js_clear_native(value, STRING_DECODER_NATIVE_TAG); } static int sd_parse_encoding(const char *s, size_t len) { static const struct { const char *label; uint8_t llen; int enc; } map[] = { {"utf8", 4, SD_ENC_UTF8}, {"utf-8", 5, SD_ENC_UTF8}, {"utf16le", 7, SD_ENC_UTF16LE}, {"utf-16le", 8, SD_ENC_UTF16LE}, {"ucs2", 4, SD_ENC_UTF16LE}, {"ucs-2", 5, SD_ENC_UTF16LE}, {"latin1", 6, SD_ENC_LATIN1}, {"binary", 6, SD_ENC_LATIN1}, {"ascii", 5, SD_ENC_LATIN1}, {"hex", 3, SD_ENC_HEX}, {"base64", 6, SD_ENC_BASE64}, {"base64url", 9, SD_ENC_BASE64}, {NULL, 0, 0} }; for (int i = 0; map[i].label; i++) { if (len == map[i].llen && strncasecmp(s, map[i].label, len) == 0) return map[i].enc; } return SD_ENC_UTF8; } static const char *sd_encoding_name(int enc) { switch (enc) { case SD_ENC_UTF16LE: return "utf-16le"; case SD_ENC_LATIN1: return "latin1"; case SD_ENC_HEX: return "hex"; case SD_ENC_BASE64: return "base64"; default: return "utf-8"; }} static ant_value_t sd_latin1_to_str(ant_t *js, const uint8_t *src, size_t len) { size_t out_len = 0; char *out = latin1_to_utf8(src, len, &out_len); if (!out) return js_mkerr(js, "out of memory"); ant_value_t result = js_mkstr(js, out, out_len); free(out); return result; } static ant_value_t sd_hex_decode(ant_t *js, const uint8_t *src, size_t len) { if (len == 0) return js_mkstr(js, "", 0); char *out = malloc(len * 2 + 1); if (!out) return js_mkerr(js, "out of memory"); hex_encode(src, len, out); ant_value_t result = js_mkstr(js, out, len * 2); free(out); return result; } static ant_value_t sd_base64_write(ant_t *js, sd_state_t *st, const uint8_t *src, size_t len, bool flush) { size_t total = (size_t)st->pending_len + len; if (total == 0) return js_mkstr(js, "", 0); uint8_t *work = malloc(total); if (!work) return js_mkerr(js, "out of memory"); if (st->pending_len > 0) memcpy(work, st->pending, (size_t)st->pending_len); if (src && len > 0) memcpy(work + st->pending_len, src, len); size_t encode_len = flush ? total : (total / 3) * 3; int new_pending = (int)(total - encode_len); ant_value_t result; if (encode_len == 0) result = js_mkstr(js, "", 0); else { size_t out_len; char *out = ant_base64_encode(work, encode_len, &out_len); if (!out) { free(work); return js_mkerr(js, "out of memory"); } result = js_mkstr(js, out, out_len); free(out); } st->pending_len = new_pending; if (new_pending > 0) memcpy(st->pending, work + encode_len, (size_t)new_pending); free(work); return result; } static ant_value_t sd_do_write(ant_t *js, sd_state_t *st, const uint8_t *src, size_t len, bool flush) { switch (st->encoding) { case SD_ENC_UTF8: case SD_ENC_UTF16LE: case SD_ENC_UTF16BE: return td_decode(js, st->td, src, len, !flush); case SD_ENC_LATIN1: if (!src || len == 0) return js_mkstr(js, "", 0); return sd_latin1_to_str(js, src, len); case SD_ENC_HEX: if (!src || len == 0) return js_mkstr(js, "", 0); return sd_hex_decode(js, src, len); case SD_ENC_BASE64: return sd_base64_write(js, st, src, len, flush); default: return js_mkstr(js, "", 0); }} static ant_value_t js_sd_get_encoding(ant_params_t) { sd_state_t *st = sd_get_state(js->this_val); if (!st) return js_mkstr(js, "utf-8", 5); const char *name = sd_encoding_name(st->encoding); return js_mkstr(js, name, strlen(name)); } static ant_value_t js_sd_write(ant_params_t) { sd_state_t *st = sd_get_state(js->this_val); if (!st) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid StringDecoder"); if (nargs < 1) return js_mkstr(js, "", 0); if (vtype(args[0]) == kTypeString) { size_t slen; const char *s = js_getstr(js, args[0], &slen); return s ? js_mkstr(js, s, slen) : js_mkstr(js, "", 0); } const uint8_t *src = NULL; size_t len = 0; if (is_object_type(args[0])) buffer_source_get_bytes(js, args[0], &src, &len); return sd_do_write(js, st, src, len, false); } static ant_value_t js_sd_end(ant_params_t) { sd_state_t *st = sd_get_state(js->this_val); if (!st) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid StringDecoder"); const uint8_t *src = NULL; size_t len = 0; if (nargs > 0 && is_object_type(args[0])) buffer_source_get_bytes(js, args[0], &src, &len); return sd_do_write(js, st, src, len, true); } static inline __attribute__((always_inline)) int sd_encoding_from_value(ant_t *js, ant_value_t encoding) { int enc = SD_ENC_UTF8; if (!is_undefined(encoding)) { ant_value_t label_val = (vtype(encoding) == kTypeString) ? encoding : coerce_to_str(js, encoding); if (!is_err(label_val) && vtype(label_val) == kTypeString) { size_t llen; const char *label = js_getstr(js, label_val, &llen); if (label) enc = sd_parse_encoding(label, llen); }} return enc; } static inline __attribute__((always_inline)) sd_state_t *sd_state_new(int enc) { sd_state_t *st = calloc(1, sizeof(sd_state_t)); if (!st) return NULL; st->encoding = enc; if (enc == SD_ENC_UTF8 || enc == SD_ENC_UTF16LE || enc == SD_ENC_UTF16BE) { td_encoding_t td_enc = (enc == SD_ENC_UTF16LE) ? TD_ENC_UTF16LE : (enc == SD_ENC_UTF16BE) ? TD_ENC_UTF16BE : TD_ENC_UTF8; st->td = td_state_new(td_enc, false, false); if (!st->td) { free(st); return NULL; } } return st; } static inline __attribute__((always_inline)) void sd_attach(ant_value_t obj, ant_value_t proto, sd_state_t *st) { if (is_object_type(proto)) js_set_proto_init(obj, proto); js_set_native(obj, st, STRING_DECODER_NATIVE_TAG); js_set_finalizer(obj, sd_finalize); } ant_value_t string_decoder_create(ant_t *js, ant_value_t encoding, ant_value_t new_target) { sd_state_t *st = sd_state_new(sd_encoding_from_value(js, encoding)); if (!st) return js_mkerr(js, "out of memory"); ant_value_t obj = Ant_Object_CreateBare(js); ant_value_t proto = js_instance_proto_from_new_target(js, js->builtins.string_decoder_proto, new_target); sd_attach(obj, proto, st); return obj; } ant_value_t string_decoder_decode_value( ant_t *js, ant_value_t decoder, ant_value_t chunk, bool flush ) { sd_state_t *st = sd_get_state(decoder); if (!st) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid StringDecoder"); if (vtype(chunk) == kTypeString) { size_t slen = 0; const char *s = js_getstr(js, chunk, &slen); return s ? js_mkstr(js, s, slen) : js_mkstr(js, "", 0); } const uint8_t *src = NULL; size_t len = 0; if (is_object_type(chunk)) buffer_source_get_bytes(js, chunk, &src, &len); return sd_do_write(js, st, src, len, flush); } // StringDecoder.call(this, enc) without new initializes `this` in place static __attribute__((noinline)) ant_value_t sd_init_this(ant_t *js, ant_value_t self, ant_value_t encoding) { if (sd_get_state(self)) return self; sd_state_t *st = sd_state_new(sd_encoding_from_value(js, encoding)); if (!st) return js_mkerr(js, "out of memory"); sd_attach(self, js->builtins.string_decoder_proto, st); return self; } static ant_value_t js_sd_ctor(ant_params_t) { ant_value_t encoding = nargs > 0 ? args[0] : js_mkundef(); if (!is_undefined(call_new_target) || !is_object_type(js->this_val)) return string_decoder_create(js, encoding, call_new_target); return sd_init_this(js, js->this_val, encoding); } ant_value_t string_decoder_library(ant_t *js) { js->builtins.string_decoder_proto = Ant_Object_CreateBare(js); js_set_getter_desc(js, js->builtins.string_decoder_proto, "encoding", 8, Ant_Function_Create(js_sd_get_encoding), JS_DESC_C); js_set(js, js->builtins.string_decoder_proto, "write", Ant_Function_CreateWithArity(js_sd_write, 1)); js_set(js, js->builtins.string_decoder_proto, "end", Ant_Function_CreateWithArity(js_sd_end, 1)); js_set_tostringtag(js, js->builtins.string_decoder_proto, js_mkstr(js, "StringDecoder", 13)); ant_value_t ctor = Ant_Function_CreateConstructor(js, js_sd_ctor, js->builtins.string_decoder_proto, "StringDecoder", 1); ant_value_t lib = Ant_Object_CreateBare(js); js_set(js, lib, "StringDecoder", ctor); return lib; }