diff --git a/meson.build b/meson.build index 7c5aa2c7..b8d573b2 100644 --- a/meson.build +++ b/meson.build @@ -65,6 +65,22 @@ endif cmake = import('cmake') +mbedtls_opts = cmake.subproject_options() +mbedtls_opts.add_cmake_defines({ + 'ENABLE_PROGRAMS': 'OFF', # Disable Mbed TLS programs + 'ENABLE_TESTING': 'OFF', # Disable Mbed TLS tests + 'CMAKE_BUILD_TYPE': 'Release', # Optimize for release + 'MBEDTLS_FATAL_WARNINGS': 'ON', # Treat warnings as errors + 'USE_STATIC_MBEDTLS_LIBRARY': 'ON',# Build static libraries + 'USE_SHARED_MBEDTLS_LIBRARY': 'OFF'# Disable shared libraries +}) +mbedtls_proj = cmake.subproject('mbedtls', options: mbedtls_opts) +deps += [ + mbedtls_proj.dependency('mbedtls'), + mbedtls_proj.dependency('mbedx509'), + mbedtls_proj.dependency('mbedcrypto') +] + sdl3_opts = cmake.subproject_options() sdl3_opts.add_cmake_defines({ 'SDL_STATIC': 'ON', @@ -116,7 +132,6 @@ else deps += sdl3_proj.dependency('SDL3-static') endif - quickjs_opts = [] quickjs_opts += 'default_library=static' @@ -156,76 +171,6 @@ src += [ 'thirdparty/quirc/identify.c', 'thirdparty/quirc/version_db.c' ] -curl_opts = [ - 'http=enabled', - 'ssl=enabled', - 'openssl=enabled', - 'schannel=disabled', - 'secure-transport=disabled', - 'dict=disabled', - 'file=disabled', - 'ftp=disabled', - 'gopher=disabled', - 'imap=disabled', - 'ldap=disabled', - 'ldaps=disabled', - 'mqtt=disabled', - 'pop3=disabled', - 'rtmp=disabled', - 'rtsp=disabled', - 'smb=disabled', - 'smtp=disabled', - 'telnet=disabled', - 'tftp=disabled', - 'alt-svc=disabled', - 'asynchdns=disabled', - 'aws=disabled', - 'basic-auth=disabled', - 'bearer-auth=disabled', - 'bindlocal=disabled', - 'brotli=disabled', - 'cookies=disabled', - 'digest-auth=disabled', - 'doh=disabled', - 'form-api=disabled', - 'getoptions=disabled', - 'gsasl=disabled', - 'gss-api=disabled', - 'headers-api=disabled', - 'hsts=disabled', - 'http2=disabled', - 'idn=disabled', - 'kerberos-auth=disabled', - 'libcurl-option=disabled', - 'libz=disabled', - 'mime=disabled', - 'negotiate-auth=disabled', - 'netrc=disabled', - 'ntlm=disabled', - 'parsedate=disabled', - 'progress-meter=disabled', - 'proxy=disabled', - 'psl=disabled', - 'sha512_256=disabled', - 'shuffle-dns=disabled', - 'socketpair=disabled', - 'tls-srp=disabled', - 'unixsockets=disabled', - 'verbose-strings=disabled', - 'zstd=disabled', - 'debug=disabled', - 'curldebug=false', - 'libuv=disabled', - 'tests=disabled', - 'unittests=disabled', - 'default_library=static' -] - -curl_proj = subproject('curl', default_options: curl_opts) -deps += dependency('libcurl') -deps += dependency('zlib', static: true) -deps += dependency('openssl', static:true) - imsrc = [ 'GraphEditor.cpp','ImCurveEdit.cpp','ImGradient.cpp','imgui_draw.cpp', 'imgui_tables.cpp','imgui_widgets.cpp','imgui.cpp','ImGuizmo.cpp','imnodes.cpp', diff --git a/scripts/modules/http.js b/scripts/modules/http.js index 2c3d5e1f..f6e13c3d 100644 --- a/scripts/modules/http.js +++ b/scripts/modules/http.js @@ -15,12 +15,4 @@ This function enqueues an HTTP GET request for the specified URL. It supports bo - An error if memory allocation or CURL initialization fails. ` -http.poll[prosperon.DOC] = `Process pending HTTP requests and invoke callbacks. - -This function checks for I/O activity on all enqueued HTTP requests and processes any that have completed or received data. For completed requests, it invokes the 'callback' function (if provided) with the result. For streaming requests, it invokes the 'data' function (if provided) as data arrives. This function must be called repeatedly to drive the asynchronous request system. - -:return: undefined -:throws: An error if CURL multi-handle processing fails. -` - return http diff --git a/source/qjs_http.c b/source/qjs_http.c index 18854127..f5cf72c9 100644 --- a/source/qjs_http.c +++ b/source/qjs_http.c @@ -1,269 +1,368 @@ #include "quickjs.h" -#include -#include +#include #include +#include +#include +#include +#include +#include +#include -// ----------------------------------------------------------------------------- -// HTTP request structure -// ----------------------------------------------------------------------------- typedef struct { - char url[512]; // URL for the request - JSContext *ctx; // JS context for callbacks - JSValue callback; // Completion callback (optional) - JSValue on_data; // Streaming data callback (optional) - char *response; // Buffer for non-streaming mode - size_t size; // Size of response buffer - CURL *curl; // CURL easy handle - int done; // Request completion flag - int curl_result; // CURL result code -} HttpRequest; + char *data; + size_t size; + size_t capacity; +} buffer_t; -// ----------------------------------------------------------------------------- -// Global data -// ----------------------------------------------------------------------------- -static CURLM *g_curl_multi = NULL; // CURL multi-handle for async I/O - -// ----------------------------------------------------------------------------- -// Libcurl write callback for streaming or buffering -// ----------------------------------------------------------------------------- -static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) -{ - size_t realsize = size * nmemb; - HttpRequest *req = (HttpRequest *)userp; - - // Streaming mode: call onData if provided - if (JS_IsFunction(req->ctx, req->on_data)) { - JSValue chunk = JS_NewStringLen(req->ctx, contents, realsize); - JSValue ret = JS_Call(req->ctx, req->on_data, JS_UNDEFINED, 1, &chunk); - JS_FreeValue(req->ctx, chunk); - JS_FreeValue(req->ctx, ret); // Ignore return value - return realsize; - } - - // Non-streaming mode: buffer the response - char *ptr = realloc(req->response, req->size + realsize + 1); - if (!ptr) { - return 0; // Out of memory, tell CURL to abort - } - req->response = ptr; - memcpy(&(req->response[req->size]), contents, realsize); - req->size += realsize; - req->response[req->size] = '\0'; // Null-terminate - return realsize; +static void buffer_init(buffer_t *buf) { + buf->data = NULL; + buf->size = 0; + buf->capacity = 0; } -// ----------------------------------------------------------------------------- -// JS function: http.fetch(url, options) -// - Enqueues an async HTTP request with optional streaming -// ----------------------------------------------------------------------------- -static JSValue js_http_fetch(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - if (argc < 2 || !JS_IsString(argv[0])) { - return JS_ThrowTypeError(ctx, "fetch expects a URL string and an options object or callback"); +static int buffer_append(buffer_t *buf, const void *data, size_t len) { + if (buf->size + len > buf->capacity) { + size_t new_capacity = buf->capacity ? buf->capacity * 2 : 4096; + while (new_capacity < buf->size + len) { + new_capacity *= 2; + } + char *new_data = realloc(buf->data, new_capacity); + if (!new_data) return -1; + buf->data = new_data; + buf->capacity = new_capacity; } + memcpy(buf->data + buf->size, data, len); + buf->size += len; + return 0; +} - // Get URL +static void buffer_free(buffer_t *buf) { + free(buf->data); + buf->data = NULL; + buf->size = 0; + buf->capacity = 0; +} + +// Parse URL into components +static int parse_url(const char *url, char **host, char **port, char **path, int *use_ssl) { + *host = NULL; + *port = NULL; + *path = NULL; + *use_ssl = 0; + + const char *p = url; + + // Parse scheme + if (strncmp(p, "https://", 8) == 0) { + *use_ssl = 1; + p += 8; + } else if (strncmp(p, "http://", 7) == 0) { + *use_ssl = 0; + p += 7; + } else { + return -1; // Invalid scheme + } + + // Find host end + const char *host_start = p; + const char *host_end = strchr(p, '/'); + const char *port_start = strchr(p, ':'); + + if (port_start && (!host_end || port_start < host_end)) { + // Has explicit port + *host = strndup(host_start, port_start - host_start); + port_start++; + if (host_end) { + *port = strndup(port_start, host_end - port_start); + } else { + *port = strdup(port_start); + } + } else { + // No explicit port + if (host_end) { + *host = strndup(host_start, host_end - host_start); + } else { + *host = strdup(host_start); + } + *port = strdup(*use_ssl ? "443" : "80"); + } + + // Path + if (host_end) { + *path = strdup(host_end); + } else { + *path = strdup("/"); + } + + return 0; +} + +// Perform HTTP request over plain socket +static int http_request(const char *host, const char *port, const char *request, size_t request_len, buffer_t *response) { + mbedtls_net_context server_fd; + int ret; + + mbedtls_net_init(&server_fd); + + // Connect to server + if ((ret = mbedtls_net_connect(&server_fd, host, port, MBEDTLS_NET_PROTO_TCP)) != 0) { + mbedtls_net_free(&server_fd); + return ret; + } + + // Send request + size_t written = 0; + while (written < request_len) { + ret = mbedtls_net_send(&server_fd, (unsigned char *)request + written, request_len - written); + if (ret < 0) { + mbedtls_net_free(&server_fd); + return ret; + } + written += ret; + } + + // Read response + unsigned char buf[4096]; + do { + ret = mbedtls_net_recv(&server_fd, buf, sizeof(buf)); + if (ret > 0) { + if (buffer_append(response, buf, ret) < 0) { + mbedtls_net_free(&server_fd); + return -1; + } + } + } while (ret > 0); + + mbedtls_net_free(&server_fd); + return (ret == 0 || ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) ? 0 : ret; +} + +// Perform HTTPS request over SSL +static int https_request(const char *host, const char *port, const char *request, size_t request_len, buffer_t *response) { + mbedtls_net_context server_fd; + mbedtls_ssl_context ssl; + mbedtls_ssl_config conf; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + const char *pers = "qjs_https_client"; + int ret; + + // Initialize structures + mbedtls_net_init(&server_fd); + mbedtls_ssl_init(&ssl); + mbedtls_ssl_config_init(&conf); + mbedtls_entropy_init(&entropy); + mbedtls_ctr_drbg_init(&ctr_drbg); + + // Seed RNG + if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, + (const unsigned char *)pers, strlen(pers))) != 0) { + goto exit; + } + + // Connect to server + if ((ret = mbedtls_net_connect(&server_fd, host, port, MBEDTLS_NET_PROTO_TCP)) != 0) { + goto exit; + } + + // Configure SSL + if ((ret = mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { + goto exit; + } + + mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); + mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_OPTIONAL); + + if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) { + goto exit; + } + + if ((ret = mbedtls_ssl_set_hostname(&ssl, host)) != 0) { + goto exit; + } + + mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL); + + // Handshake + while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + goto exit; + } + } + + // Send request + size_t written = 0; + while (written < request_len) { + ret = mbedtls_ssl_write(&ssl, (const unsigned char *)request + written, request_len - written); + if (ret < 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret != MBEDTLS_ERR_SSL_WANT_READ) { + goto exit; + } + } else { + written += ret; + } + } + + // Read response + unsigned char buf[4096]; + do { + ret = mbedtls_ssl_read(&ssl, buf, sizeof(buf)); + if (ret > 0) { + if (buffer_append(response, buf, ret) < 0) { + ret = -1; + goto exit; + } + } + } while (ret > 0); + + if (ret < 0 && ret != MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY && + ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + goto exit; + } + + ret = 0; + +exit: + mbedtls_ssl_close_notify(&ssl); + mbedtls_net_free(&server_fd); + mbedtls_ssl_free(&ssl); + mbedtls_ssl_config_free(&conf); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + + return ret; +} + +// Extract body from HTTP response +static char *extract_body(const char *response, size_t response_len, size_t *body_len) { + const char *body_start = strstr(response, "\r\n\r\n"); + if (!body_start) { + return NULL; + } + body_start += 4; + + *body_len = response_len - (body_start - response); + return (char *)body_start; +} + +// JS function: fetch(url, options) +static JSValue js_fetch(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + if (argc < 1 || !JS_IsString(argv[0])) { + return JS_ThrowTypeError(ctx, "fetch expects a URL string"); + } + const char *url = JS_ToCString(ctx, argv[0]); if (!url) { return JS_ThrowTypeError(ctx, "Invalid URL"); } - - // Allocate request object - HttpRequest *req = calloc(1, sizeof(*req)); - if (!req) { + + char *host = NULL; + char *port = NULL; + char *path = NULL; + int use_ssl = 0; + buffer_t request_buf; + buffer_t response_buf; + JSValue result = JS_EXCEPTION; + + buffer_init(&request_buf); + buffer_init(&response_buf); + + // Parse URL + if (parse_url(url, &host, &port, &path, &use_ssl) < 0) { JS_FreeCString(ctx, url); - return JS_ThrowInternalError(ctx, "Failed to allocate memory"); + return JS_ThrowTypeError(ctx, "Invalid URL format"); } - strncpy(req->url, url, sizeof(req->url) - 1); - req->ctx = ctx; - req->callback = JS_NULL; - req->on_data = JS_NULL; - - // Parse second argument: callback or options object - if (JS_IsFunction(ctx, argv[1])) { - req->callback = JS_DupValue(ctx, argv[1]); - } else if (JS_IsObject(argv[1])) { - JSValue callback = JS_GetPropertyStr(ctx, argv[1], "callback"); - JSValue on_data = JS_GetPropertyStr(ctx, argv[1], "on_data"); - if (JS_IsFunction(ctx, callback)) { - req->callback = JS_DupValue(ctx, callback); - } - if (JS_IsFunction(ctx, on_data)) { - req->on_data = JS_DupValue(ctx, on_data); - } - JS_FreeValue(ctx, callback); - JS_FreeValue(ctx, on_data); - } else { - JS_FreeCString(ctx, url); - free(req); - return JS_ThrowTypeError(ctx, "Second argument must be a callback or options object"); - } - - JS_FreeCString(ctx, url); - - // Initialize CURL easy handle - req->curl = curl_easy_init(); - if (!req->curl) { - JS_FreeValue(ctx, req->callback); - JS_FreeValue(ctx, req->on_data); - free(req); - return JS_ThrowInternalError(ctx, "Failed to create CURL handle"); - } - - // Set CURL options - curl_easy_setopt(req->curl, CURLOPT_URL, req->url); - curl_easy_setopt(req->curl, CURLOPT_WRITEFUNCTION, write_callback); - curl_easy_setopt(req->curl, CURLOPT_WRITEDATA, req); - curl_easy_setopt(req->curl, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(req->curl, CURLOPT_SSL_VERIFYPEER, 1L); - curl_easy_setopt(req->curl, CURLOPT_SSL_VERIFYHOST, 2L); - curl_easy_setopt(req->curl, CURLOPT_USERAGENT, "prosperon"); - curl_easy_setopt(req->curl, CURLOPT_PRIVATE, req); - - // Add to multi-handle - if (!g_curl_multi) { - curl_easy_cleanup(req->curl); - JS_FreeValue(ctx, req->callback); - JS_FreeValue(ctx, req->on_data); - free(req); - return JS_ThrowInternalError(ctx, "CURL multi-handle not initialized"); - } - - CURLMcode mc = curl_multi_add_handle(g_curl_multi, req->curl); - if (mc != CURLM_OK) { - curl_easy_cleanup(req->curl); - JS_FreeValue(ctx, req->callback); - JS_FreeValue(ctx, req->on_data); - free(req); - return JS_ThrowInternalError(ctx, "curl_multi_add_handle failed: %s", curl_multi_strerror(mc)); - } - - return JS_UNDEFINED; -} - -// ----------------------------------------------------------------------------- -// JS function: http.poll() -// - Checks for I/O and completed requests, invoking callbacks as needed -// ----------------------------------------------------------------------------- -static JSValue js_http_poll(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - if (!g_curl_multi) { - return JS_UNDEFINED; - } - - // Perform pending transfers - int still_running = 0; - CURLMcode mc = curl_multi_perform(g_curl_multi, &still_running); - if (mc != CURLM_OK) { - return JS_ThrowInternalError(ctx, "curl_multi_perform failed: %s", curl_multi_strerror(mc)); - } - - // Check for completed requests - CURLMsg *msg; - int msgq = 0; - while ((msg = curl_multi_info_read(g_curl_multi, &msgq))) { - if (msg->msg == CURLMSG_DONE) { - CURL *easy = msg->easy_handle; - HttpRequest *req = NULL; - curl_easy_getinfo(easy, CURLINFO_PRIVATE, (char **)&req); - - char *ct = NULL; - curl_easy_getinfo(easy, CURLINFO_CONTENT_TYPE, &ct); - - // Remove from multi-handle - curl_multi_remove_handle(g_curl_multi, easy); - - // Mark as done - req->curl_result = msg->data.result; - req->done = 1; - - // Call completion callback if provided - if (JS_IsFunction(req->ctx, req->callback)) { - JSValue arg = JS_NewObject(req->ctx); - if (req->curl_result == CURLE_OK) { - JS_SetPropertyStr(req->ctx, arg, "data", - JS_NewArrayBufferCopy(req->ctx, req->response, req->size)); - JS_SetPropertyStr(req->ctx, arg, "error", JS_UNDEFINED); + + // Build request + buffer_append(&request_buf, "GET ", 4); + buffer_append(&request_buf, path, strlen(path)); + buffer_append(&request_buf, " HTTP/1.1\r\n", 11); + buffer_append(&request_buf, "Host: ", 6); + buffer_append(&request_buf, host, strlen(host)); + buffer_append(&request_buf, "\r\n", 2); + + // Add headers from options if provided + if (argc >= 2 && JS_IsObject(argv[1])) { + JSValue headers = JS_GetPropertyStr(ctx, argv[1], "headers"); + if (JS_IsObject(headers)) { + JSPropertyEnum *tab; + uint32_t len; + if (JS_GetOwnPropertyNames(ctx, &tab, &len, headers, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) == 0) { + for (uint32_t i = 0; i < len; i++) { + JSValue key = JS_AtomToString(ctx, tab[i].atom); + JSValue val = JS_GetProperty(ctx, headers, tab[i].atom); - if (ct) JS_SetPropertyStr(req->ctx, arg, "type", JS_NewString(req->ctx, ct)); - } else { - JS_DefinePropertyValueStr(req->ctx, arg, "data", JS_NULL, JS_PROP_C_W_E); - const char *err_str = curl_easy_strerror(req->curl_result); - JS_DefinePropertyValueStr(req->ctx, arg, "error", - JS_NewString(req->ctx, err_str ? err_str : "Unknown error"), - JS_PROP_C_W_E); + const char *key_str = JS_ToCString(ctx, key); + const char *val_str = JS_ToCString(ctx, val); + + if (key_str && val_str) { + buffer_append(&request_buf, key_str, strlen(key_str)); + buffer_append(&request_buf, ": ", 2); + buffer_append(&request_buf, val_str, strlen(val_str)); + buffer_append(&request_buf, "\r\n", 2); + } + + JS_FreeCString(ctx, key_str); + JS_FreeCString(ctx, val_str); + JS_FreeValue(ctx, key); + JS_FreeValue(ctx, val); } - JSValue ret = JS_Call(req->ctx, req->callback, JS_UNDEFINED, 1, &arg); - JS_FreeValue(req->ctx, arg); - JS_FreeValue(req->ctx, ret); + js_free(ctx, tab); } - - // Cleanup - JS_FreeValue(req->ctx, req->callback); - JS_FreeValue(req->ctx, req->on_data); - curl_easy_cleanup(req->curl); - free(req->response); - free(req); } + JS_FreeValue(ctx, headers); } - - return JS_UNDEFINED; + + buffer_append(&request_buf, "Connection: close\r\n\r\n", 21); + + // Perform request + int ret; + if (use_ssl) { + ret = https_request(host, port, request_buf.data, request_buf.size, &response_buf); + } else { + ret = http_request(host, port, request_buf.data, request_buf.size, &response_buf); + } + + if (ret == 0 && response_buf.data) { + // Extract body + size_t body_len; + char *body = extract_body(response_buf.data, response_buf.size, &body_len); + + if (body) { + // Return body as ArrayBuffer + result = JS_NewArrayBufferCopy(ctx, (uint8_t *)body, body_len); + } else { + result = JS_ThrowInternalError(ctx, "Failed to parse HTTP response"); + } + } else { + char error_buf[256]; + mbedtls_strerror(ret, error_buf, sizeof(error_buf)); + result = JS_ThrowInternalError(ctx, "Request failed: %s", error_buf); + } + + // Cleanup + JS_FreeCString(ctx, url); + free(host); + free(port); + free(path); + buffer_free(&request_buf); + buffer_free(&response_buf); + + return result; } -// ----------------------------------------------------------------------------- -// Module initialization -// ----------------------------------------------------------------------------- +#define countof(a) (sizeof(a)/sizeof(*(a))) + +// Module exports static const JSCFunctionListEntry js_http_funcs[] = { - JS_CFUNC_DEF("fetch", 2, js_http_fetch), - JS_CFUNC_DEF("poll", 0, js_http_poll), + JS_CFUNC_DEF("fetch", 2, js_fetch), }; -JSValue js_http_use(JSContext *ctx) +JSValue js_http_use(JSContext *js) { - // Initialize CURL globally (once per process) - static int s_curl_init_done = 0; - if (!s_curl_init_done) { - s_curl_init_done = 1; - if (curl_global_init(CURL_GLOBAL_ALL) != 0) { - return JS_ThrowInternalError(ctx, "Failed to initialize CURL"); - } - } - - // Initialize multi-handle (once per module) - if (!g_curl_multi) { - g_curl_multi = curl_multi_init(); - if (!g_curl_multi) { - return JS_ThrowInternalError(ctx, "Failed to initialize CURL multi-handle"); - } - } - - // Export fetch and poll functions - JSValue export_obj = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, export_obj, js_http_funcs, - sizeof(js_http_funcs) / sizeof(JSCFunctionListEntry)); - return export_obj; -} - -static int js_http_init(JSContext *ctx, JSModuleDef *m) -{ - JS_SetModuleExport(ctx, m, "default", js_http_use(ctx)); - return 0; -} - -#ifdef JS_SHARED_LIBRARY -#define JS_INIT_MODULE js_init_module -#else -#define JS_INIT_MODULE js_init_module_http -#endif - -JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) -{ - JSModuleDef *m = JS_NewCModule(ctx, module_name, js_http_init); - if (!m) { - return NULL; - } - JS_AddModuleExport(ctx, m, "default"); - return m; + JSValue exp = JS_NewObject(js); + JS_SetPropertyFunctionList(js, exp, js_http_funcs, countof(js_http_funcs)); + return exp; } diff --git a/source/qjs_os.c b/source/qjs_os.c index 978c0775..2dcd1a8b 100644 --- a/source/qjs_os.c +++ b/source/qjs_os.c @@ -193,6 +193,32 @@ JSC_CCALL(os_on, rt->on_exception = JS_DupValue(js,argv[1]); ) +JSC_CCALL(os_buffer2string, + if (argc < 1) { + return JS_ThrowTypeError(js, "buffer2string expects an ArrayBuffer"); + } + + size_t len; + uint8_t *buf = JS_GetArrayBuffer(js, &len, argv[0]); + if (!buf) { + return JS_ThrowTypeError(js, "First argument must be an ArrayBuffer"); + } + + // Create a null-terminated string from the buffer + char *str = js_malloc(js, len + 1); + if (!str) { + return JS_ThrowInternalError(js, "Failed to allocate memory"); + } + + memcpy(str, buf, len); + str[len] = '\0'; + + JSValue result = JS_NewString(js, str); + js_free(js, str); + + return result; +) + #define JSOBJ_ADD_FIELD(OBJ, STRUCT, FIELD, TYPE) \ JS_SetPropertyStr(js, OBJ, #FIELD, TYPE##2js(js,STRUCT.FIELD));\ @@ -254,6 +280,7 @@ static const JSCFunctionListEntry js_os_funcs[] = { MIST_FUNC_DEF(os, on, 2), MIST_FUNC_DEF(os, rusage, 0), MIST_FUNC_DEF(os, mallinfo, 0), + MIST_FUNC_DEF(os, buffer2string, 1), }; JSValue js_os_use(JSContext *js) { diff --git a/subprojects/mbedtls.wrap b/subprojects/mbedtls.wrap new file mode 100644 index 00000000..1352a20a --- /dev/null +++ b/subprojects/mbedtls.wrap @@ -0,0 +1,4 @@ +[wrap-git] +url = https://github.com/Mbed-TLS/mbedtls.git +revision = v3.6.3.1 +depth = 1 diff --git a/tests/https.js b/tests/https.js new file mode 100644 index 00000000..ee101794 --- /dev/null +++ b/tests/https.js @@ -0,0 +1,6 @@ +var http = use('http') +var os = use('os') + +var res = http.fetch("https://dictionary.ink/find?word=palm") + +console.log(os.buffer2string(res)) \ No newline at end of file