Something went wrong. Try again.
javascript for 🐜's, a tiny runtime with big ambitions antjs.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497#include <stdlib.h>#include <string.h>#include <stdint.h>#include <inttypes.h>#include <time.h>#include <ctype.h>#include <stdio.h>
#include "ant.h"#include "cfunc.h"#include "ptr.h"#include "errors.h"#include "internal.h"#include "descriptors.h"
#include "modules/blob.h"#include "modules/buffer.h"#include "modules/iterator.h"#include "streams/readable.h"
static constexpr uint32_t BLOB_NATIVE_TAG = Ant_NativeTag("BLOB");
bool blob_is_blob(ant_t *js, ant_value_t obj) { int id = js_brand_id(obj); return id == BRAND_BLOB || id == BRAND_FILE;}
blob_data_t *blob_get_data(ant_value_t obj) { return (blob_data_t *)js_get_native(obj, BLOB_NATIVE_TAG);}
blob_storage_t *blob_storage_retain(blob_storage_t *storage) { if (storage) atomic_fetch_add_explicit(&storage->refs, 1, memory_order_relaxed); return storage;}
void blob_storage_release(blob_storage_t *storage) { if (storage && atomic_fetch_sub_explicit(&storage->refs, 1, memory_order_acq_rel) == 1) free(storage);}
static blob_data_t *blob_data_adopt(blob_storage_t *storage, const char *type) { blob_data_t *bd = calloc(1, sizeof(blob_data_t)); if (!bd) return NULL;
bd->storage = storage; bd->data = storage ? storage->bytes : NULL; bd->size = storage ? storage->size : 0; bd->type = type ? strdup(type) : strdup("");
return bd;}
static blob_data_t *blob_data_new(const uint8_t *data, size_t size, const char *type) { blob_storage_t *storage = NULL; if (size > 0 && data) { if (size > SIZE_MAX - sizeof(*storage)) return NULL; storage = malloc(sizeof(*storage) + size); if (!storage) return NULL; atomic_init(&storage->refs, 1); storage->size = size; memcpy(storage->bytes, data, size); }
blob_data_t *bd = blob_data_adopt(storage, type); if (!bd) blob_storage_release(storage); else bd->size = size;
return bd;}
static char *normalize_mime_type(const char *s) { if (!s) return strdup(""); for (const unsigned char *p = (const unsigned char *)s; *p; p++) if (*p < 0x20 || *p > 0x7E) return strdup(""); size_t len = strlen(s); char *out = malloc(len + 1); if (!out) return strdup(""); for (size_t i = 0; i <= len; i++) out[i] = (char)tolower((unsigned char)s[i]); return out;}
typedef struct { uint8_t *buf; size_t size; size_t cap;} byte_buf_t;
static bool byte_buf_grow(byte_buf_t *b, size_t extra) { size_t needed = b->size + extra; if (needed <= b->cap) return true; size_t new_cap = b->cap ? b->cap * 2 : 64; while (new_cap < needed) new_cap *= 2; uint8_t *p = realloc(b->buf, new_cap); if (!p) return false; b->buf = p; b->cap = new_cap; return true;}
static bool byte_buf_append(byte_buf_t *b, const uint8_t *data, size_t len) { if (!byte_buf_grow(b, len)) return false; memcpy(b->buf + b->size, data, len); b->size += len; return true;}
static bool blob_part_buffer_source(ant_value_t part, uint8_t **bytes, size_t *size, bool *live) { TypedArrayData *ta = buffer_get_typedarray_data(part); DataViewData *dv = ta ? NULL : buffer_get_dataview_data(part); ArrayBufferData *abd = ta || dv ? NULL : buffer_get_arraybuffer_data(part);
if (ta) *live = Ant_Buffer_GetViewBytes(ta, bytes, size); else if (dv) *live = Ant_Buffer_GetViewBytes(dv, bytes, size); else if (abd) { *live = Ant_Buffer_IsLive(abd); *bytes = *live ? abd->data : NULL; *size = *live ? abd->length : 0; } else return false;
return true;}
static ant_value_t process_blob_part(ant_t *js, byte_buf_t *buf, ant_value_t part) { uint8_t t = vtype(part); uint8_t *bytes; size_t size; bool live;
if (blob_part_buffer_source(part, &bytes, &size, &live)) { if (!live) return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot use a detached ArrayBuffer in a Blob"); if (!byte_buf_append(buf, bytes, size)) return js_mkerr(js, "out of memory"); return js_mkundef(); }
blob_data_t *bd = t == kTypeObject ? blob_get_data(part) : NULL; if (bd) { if (!byte_buf_append(buf, bd->data, bd->size)) return js_mkerr(js, "out of memory"); return js_mkundef(); }
ant_value_t str = (t == kTypeString) ? part : js_tostring_val(js, part); if (is_err(str)) return str;
size_t len; char *s = js_getstr(js, str, &len); if (s && len > 0) { if (!byte_buf_append(buf, (const uint8_t *)s, len)) return js_mkerr(js, "out of memory"); } return js_mkundef();}
static ant_value_t process_blob_parts(ant_t *js, byte_buf_t *buf, ant_value_t parts) { uint8_t t = vtype(parts); if (t == kTypeUndefined || t == kTypeNull) return js_mkundef();
if (t != kTypeObject && t != kTypeArray && t != kTypeFunction) return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Blob': The provided value cannot be converted to a sequence.");
iterator_t it; if (!js_iter_open(js, parts, &it)) return Ant_Exception_Pending(js) ? Ant_Exception_Current(js) : js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Blob': The provided value is not of type 'BlobPart'");
ant_value_t value; while (js_iter_next(js, &it, &value)) { ant_value_t r = process_blob_part(js, buf, value); if (is_err(r)) { js_iter_close(js, &it); return r; } } return Ant_Exception_Pending(js) ? Ant_Exception_Current(js) : js_mkundef();}
static void blob_finalize(ant_t *js, ant_object_t *obj) { ant_value_t value = js_obj_from_ptr(obj); blob_data_t *bd = (blob_data_t *)js_get_native(value, BLOB_NATIVE_TAG); if (bd) { blob_storage_release(bd->storage); free(bd->type); free(bd->name); free(bd); } js_clear_native(value, BLOB_NATIVE_TAG);}
static ant_value_t blob_wrap(ant_t *js, blob_data_t *bd) { if (!bd) return js_mkerr(js, "out of memory"); ant_value_t obj = Ant_Object_CreateBare(js);
js_set_proto_init(obj, js->builtins.blob_proto); js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_BLOB)); js_set_native(obj, bd, BLOB_NATIVE_TAG); js_set_finalizer(obj, blob_finalize);
return obj;}
ant_value_t blob_create(ant_t *js, const uint8_t *data, size_t size, const char *type) { return blob_wrap(js, blob_data_new(data, size, type));}
ant_value_t blob_create_shared(ant_t *js, blob_storage_t *storage, const char *type) { blob_data_t *bd = blob_data_adopt(blob_storage_retain(storage), type); if (!bd) blob_storage_release(storage); return blob_wrap(js, bd);}
static ant_value_t blob_get_size(ant_params_t) { (void)args; (void)nargs; blob_data_t *bd = blob_get_data(js->this_val); return js_mknum(bd ? (double)bd->size : 0);}
static ant_value_t blob_get_type(ant_params_t) { (void)args; (void)nargs; blob_data_t *bd = blob_get_data(js->this_val); if (!bd || !bd->type) return js_mkstr(js, "", 0); return js_mkstr(js, bd->type, strlen(bd->type));}
static ant_value_t file_get_name(ant_params_t) { (void)args; (void)nargs; blob_data_t *bd = blob_get_data(js->this_val); if (!bd || !bd->name) return js_mkstr(js, "", 0); return js_mkstr(js, bd->name, strlen(bd->name));}
static ant_value_t file_get_last_modified(ant_params_t) { (void)args; (void)nargs; blob_data_t *bd = blob_get_data(js->this_val); return js_mknum(bd ? (double)bd->last_modified : 0);}
static ant_value_t js_blob_text(ant_params_t) { (void)args; (void)nargs; blob_data_t *bd = blob_get_data(js->this_val); ant_value_t promise = js_mkpromise(js); ant_value_t str = (!bd || bd->size == 0) ? js_mkstr(js, "", 0) : js_mkstr(js, (const char *)bd->data, bd->size); js_resolve_promise(js, promise, str); return promise;}
static ant_value_t js_blob_array_buffer(ant_params_t) { (void)args; (void)nargs; blob_data_t *bd = blob_get_data(js->this_val); ant_value_t promise = js_mkpromise(js);
size_t sz = (bd && bd->data) ? bd->size : 0; ant_value_t ab = create_arraybuffer_copy(js, sz ? bd->data : NULL, sz); if (is_err(ab)) js_reject_promise(js, promise, ab); else js_resolve_promise(js, promise, ab);
return promise;}
static ant_value_t js_blob_bytes(ant_params_t) { blob_data_t *bd = blob_get_data(js->this_val); ant_value_t promise = js_mkpromise(js);
size_t sz = (bd && bd->data) ? bd->size : 0; ArrayBufferData *abd = create_array_buffer_data(js, sz);
if (!abd) { js_reject_promise(js, promise, js_mkerr(js, "out of memory")); return promise; } if (sz > 0 && bd) memcpy(abd->data, bd->data, sz);
ant_value_t bytes = create_typed_array(js, TYPED_ARRAY_UINT8, abd, 0, sz, js->builtins.typedarray_protos[TYPED_ARRAY_UINT8]);
if (is_err(bytes)) js_reject_promise(js, promise, bytes); else js_resolve_promise(js, promise, bytes);
return promise;}
static ant_value_t js_blob_slice(ant_params_t) { blob_data_t *bd = blob_get_data(js->this_val); size_t blob_size = bd ? bd->size : 0;
ssize_t start = 0; if (nargs >= 1 && vtype(args[0]) != kTypeUndefined) { double d = js_to_number(js, args[0]); start = (ssize_t)d; if (start < 0) start = (ssize_t)blob_size + start; if (start < 0) start = 0; if ((size_t)start > blob_size) start = (ssize_t)blob_size; }
ssize_t end = (ssize_t)blob_size; if (nargs >= 2 && vtype(args[1]) != kTypeUndefined) { double d = js_to_number(js, args[1]); end = (ssize_t)d; if (end < 0) end = (ssize_t)blob_size + end; if (end < 0) end = 0; if ((size_t)end > blob_size) end = (ssize_t)blob_size; }
if (end < start) end = start;
size_t new_size = (size_t)(end - start); const uint8_t *src = (bd && bd->data && new_size > 0) ? (bd->data + start) : NULL;
const char *new_type = (bd && bd->type) ? bd->type : ""; char *type_owned = NULL; if (nargs >= 3 && vtype(args[2]) != kTypeUndefined) { ant_value_t tv = args[2]; if (vtype(tv) != kTypeString) { tv = js_tostring_val(js, tv); if (is_err(tv)) return tv; } type_owned = normalize_mime_type(js_getstr(js, tv, NULL)); new_type = type_owned; }
ant_value_t result = blob_create(js, src, new_size, new_type); free(type_owned); return result;}
static ant_value_t blob_stream_pull(ant_params_t) { ant_value_t blob_obj = js_get_slot(js->current_func, SLOT_DATA); blob_data_t *bd = blob_get_data(blob_obj); ant_value_t ctrl = (nargs > 0) ? args[0] : js_mkundef();
if (bd && bd->size > 0 && bd->data) { ArrayBufferData *ab = create_array_buffer_data(js, bd->size); if (ab) { memcpy(ab->data, bd->data, bd->size); rs_controller_enqueue(js, ctrl, create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, bd->size, js->builtins.typedarray_protos[TYPED_ARRAY_UINT8])); }}
rs_controller_close(js, ctrl); return js_mkundef();}
static ant_value_t js_blob_stream(ant_params_t) { ant_value_t pull_fn = js_heavy_mkfun(js, blob_stream_pull, js->this_val); return rs_create_stream(js, pull_fn, js_mkundef(), 1);}
static ant_value_t js_blob_ctor(ant_params_t) { if (vtype(call_new_target) == kTypeUndefined) return js_mkerr_typed(js, JS_ERR_TYPE, "Blob constructor requires 'new'");
byte_buf_t buf = {NULL, 0, 0};
if (nargs >= 1 && vtype(args[0]) != kTypeUndefined) { uint8_t pt = vtype(args[0]); if (pt != kTypeObject && pt != kTypeArray && pt != kTypeFunction) return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Blob': The provided value cannot be converted to a sequence."); ant_value_t r = process_blob_parts(js, &buf, args[0]); if (is_err(r)) { free(buf.buf); return r; } }
const char *type_str = ""; char *type_owned = NULL;
if (nargs >= 2 && vtype(args[1]) != kTypeUndefined && vtype(args[1]) != kTypeNull) { uint8_t ot = vtype(args[1]); if (ot != kTypeObject && ot != kTypeArray && ot != kTypeFunction && ot != kTypeBuiltin) { free(buf.buf); return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Blob': The 'options' argument is not an object."); } // access "endings" before "type" per lexicographic order (WPT requirement) (void)js_get(js, args[1], "endings"); ant_value_t type_v = js_get(js, args[1], "type"); if (vtype(type_v) != kTypeUndefined) { if (vtype(type_v) != kTypeString) { type_v = js_tostring_val(js, type_v); if (is_err(type_v)) { free(buf.buf); return type_v; } } type_owned = normalize_mime_type(js_getstr(js, type_v, NULL)); type_str = type_owned; } }
blob_data_t *bd = blob_data_new(buf.buf, buf.size, type_str); free(buf.buf); free(type_owned); if (!bd) 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.blob_proto, call_new_target); if (is_object_type(proto)) js_set_proto_init(obj, proto);
js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_BLOB)); js_set_native(obj, bd, BLOB_NATIVE_TAG); js_set_finalizer(obj, blob_finalize); return obj;}
static ant_value_t js_file_ctor(ant_params_t) { if (vtype(call_new_target) == kTypeUndefined) return js_mkerr_typed(js, JS_ERR_TYPE, "File constructor requires 'new'"); if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "File constructor requires at least 2 arguments");
byte_buf_t buf = {NULL, 0, 0};
if (vtype(args[0]) != kTypeUndefined) { uint8_t pt = vtype(args[0]); if (pt != kTypeObject && pt != kTypeArray && pt != kTypeFunction) { return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'File': The provided value cannot be converted to a sequence."); } ant_value_t r = process_blob_parts(js, &buf, args[0]); if (is_err(r)) { free(buf.buf); return r; } }
ant_value_t name_v = args[1]; if (vtype(name_v) != kTypeString) { name_v = js_tostring_val(js, name_v); if (is_err(name_v)) { free(buf.buf); return name_v; } } const char *name_str = js_getstr(js, name_v, NULL);
const char *type_str = ""; char *type_owned = NULL;
struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); int64_t last_modified = (int64_t)ts.tv_sec * 1000LL + (int64_t)(ts.tv_nsec / 1000000);
if (nargs >= 3 && vtype(args[2]) != kTypeUndefined && vtype(args[2]) != kTypeNull) { ant_value_t opts = args[2]; uint8_t ot = vtype(opts); if (ot == kTypeObject || ot == kTypeArray) { ant_value_t type_v = js_get(js, opts, "type"); if (vtype(type_v) != kTypeUndefined) { if (vtype(type_v) != kTypeString) { type_v = js_tostring_val(js, type_v); if (is_err(type_v)) { free(buf.buf); return type_v; } } type_owned = normalize_mime_type(js_getstr(js, type_v, NULL)); type_str = type_owned; } ant_value_t lm_v = js_get(js, opts, "lastModified"); if (vtype(lm_v) != kTypeUndefined) { double d = js_to_number(js, lm_v); if (d == d) last_modified = (int64_t)d; } } }
blob_data_t *bd = blob_data_new(buf.buf, buf.size, type_str); free(buf.buf); free(type_owned); if (!bd) return js_mkerr(js, "out of memory");
bd->name = strdup(name_str ? name_str : ""); bd->last_modified = last_modified;
ant_value_t obj = Ant_Object_CreateBare(js); ant_value_t proto = js_instance_proto_from_new_target(js, js->builtins.file_proto, call_new_target); if (is_object_type(proto)) js_set_proto_init(obj, proto);
js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_FILE)); js_set_native(obj, bd, BLOB_NATIVE_TAG); js_set_finalizer(obj, blob_finalize); return obj;}
void init_blob_module(ant_t *js) { ant_value_t g = js_glob(js); js->builtins.blob_proto = Ant_Object_CreateBare(js);
js_set_getter_desc(js, js->builtins.blob_proto, "size", 4, Ant_Function_Create(blob_get_size), JS_DESC_C); js_set_getter_desc(js, js->builtins.blob_proto, "type", 4, Ant_Function_Create(blob_get_type), JS_DESC_C);
js_set(js, js->builtins.blob_proto, "text", Ant_Function_Create(js_blob_text)); js_set(js, js->builtins.blob_proto, "arrayBuffer", Ant_Function_Create(js_blob_array_buffer)); js_set(js, js->builtins.blob_proto, "bytes", Ant_Function_Create(js_blob_bytes)); js_set(js, js->builtins.blob_proto, "slice", Ant_Function_Create(js_blob_slice)); js_set(js, js->builtins.blob_proto, "stream", Ant_Function_Create(js_blob_stream));
js_set_tostringtag(js, js->builtins.blob_proto, js_mkstr(js, "Blob", 4)); ant_value_t blob_ctor = Ant_Function_CreateIntrinsicConstructor(js, js_blob_ctor, js->builtins.blob_proto, "Blob", 0); js_set(js, g, "Blob", blob_ctor); js_set_descriptor(js, g, "Blob", 4, JS_DESC_W | JS_DESC_C);
js->builtins.file_proto = Ant_Object_CreateBare(js); js_set_proto_init(js->builtins.file_proto, js->builtins.blob_proto);
js_set_getter_desc(js, js->builtins.file_proto, "name", 4, Ant_Function_Create(file_get_name), JS_DESC_C); js_set_getter_desc(js, js->builtins.file_proto, "lastModified", 12, Ant_Function_Create(file_get_last_modified), JS_DESC_C);
js_set_tostringtag(js, js->builtins.file_proto, js_mkstr(js, "File", 4)); ant_value_t file_ctor = Ant_Function_CreateIntrinsicConstructor(js, js_file_ctor, js->builtins.file_proto, "File", 2); js_set(js, g, "File", file_ctor); js_set_descriptor(js, g, "File", 4, JS_DESC_W | JS_DESC_C);}