#include "quickjs.h" #include #include #include #include #include #include #include #include #include typedef struct { char *data; size_t size; size_t capacity; } buffer_t; typedef struct { mbedtls_net_context server_fd; mbedtls_ssl_context ssl; mbedtls_ssl_config conf; mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; int use_ssl; int headers_complete; buffer_t header_buf; size_t content_length; size_t bytes_read; int chunked_encoding; size_t chunk_size; size_t chunk_remaining; int chunk_state; // 0: size, 1: data, 2: trailer } http_connection_t; static void buffer_init(buffer_t *buf) { buf->data = NULL; buf->size = 0; buf->capacity = 0; } 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; } 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; } // Parse HTTP headers static int parse_headers(http_connection_t *conn) { char *headers_end = strstr(conn->header_buf.data, "\r\n\r\n"); if (!headers_end) { return 0; // Headers not complete } *headers_end = '\0'; // Parse Content-Length char *content_length = strstr(conn->header_buf.data, "Content-Length:"); if (!content_length) { content_length = strstr(conn->header_buf.data, "content-length:"); } if (content_length) { content_length += 15; while (*content_length == ' ') content_length++; conn->content_length = strtoul(content_length, NULL, 10); } // Check for chunked encoding char *transfer_encoding = strstr(conn->header_buf.data, "Transfer-Encoding:"); if (!transfer_encoding) { transfer_encoding = strstr(conn->header_buf.data, "transfer-encoding:"); } if (transfer_encoding) { transfer_encoding += 18; while (*transfer_encoding == ' ') transfer_encoding++; if (strncmp(transfer_encoding, "chunked", 7) == 0) { conn->chunked_encoding = 1; } } conn->headers_complete = 1; // Move any body data to the beginning of the buffer headers_end += 4; size_t body_len = conn->header_buf.size - (headers_end - conn->header_buf.data); if (body_len > 0) { memmove(conn->header_buf.data, headers_end, body_len); conn->header_buf.size = body_len; } else { conn->header_buf.size = 0; } return 1; } // Initialize HTTP connection static void http_connection_init(http_connection_t *conn) { memset(conn, 0, sizeof(http_connection_t)); mbedtls_net_init(&conn->server_fd); mbedtls_ssl_init(&conn->ssl); mbedtls_ssl_config_init(&conn->conf); mbedtls_entropy_init(&conn->entropy); mbedtls_ctr_drbg_init(&conn->ctr_drbg); buffer_init(&conn->header_buf); } // Free HTTP connection static void http_connection_free(http_connection_t *conn) { if (conn->use_ssl) { mbedtls_ssl_close_notify(&conn->ssl); } mbedtls_net_free(&conn->server_fd); mbedtls_ssl_free(&conn->ssl); mbedtls_ssl_config_free(&conn->conf); mbedtls_ctr_drbg_free(&conn->ctr_drbg); mbedtls_entropy_free(&conn->entropy); buffer_free(&conn->header_buf); } // Start HTTP connection static int http_connection_start(http_connection_t *conn, const char *host, const char *port, const char *request, size_t request_len, int use_ssl) { conn->use_ssl = use_ssl; int ret; // Connect to server if ((ret = mbedtls_net_connect(&conn->server_fd, host, port, MBEDTLS_NET_PROTO_TCP)) != 0) { return ret; } if (use_ssl) { const char *pers = "qjs_https_client"; // Seed RNG if ((ret = mbedtls_ctr_drbg_seed(&conn->ctr_drbg, mbedtls_entropy_func, &conn->entropy, (const unsigned char *)pers, strlen(pers))) != 0) { return ret; } // Configure SSL if ((ret = mbedtls_ssl_config_defaults(&conn->conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { return ret; } mbedtls_ssl_conf_rng(&conn->conf, mbedtls_ctr_drbg_random, &conn->ctr_drbg); mbedtls_ssl_conf_authmode(&conn->conf, MBEDTLS_SSL_VERIFY_OPTIONAL); if ((ret = mbedtls_ssl_setup(&conn->ssl, &conn->conf)) != 0) { return ret; } if ((ret = mbedtls_ssl_set_hostname(&conn->ssl, host)) != 0) { return ret; } mbedtls_ssl_set_bio(&conn->ssl, &conn->server_fd, mbedtls_net_send, mbedtls_net_recv, NULL); // Handshake while ((ret = mbedtls_ssl_handshake(&conn->ssl)) != 0) { if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { return ret; } } // Send request size_t written = 0; while (written < request_len) { ret = mbedtls_ssl_write(&conn->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) { return ret; } } else { written += ret; } } } else { // Send request for plain HTTP size_t written = 0; while (written < request_len) { ret = mbedtls_net_send(&conn->server_fd, (unsigned char *)request + written, request_len - written); if (ret < 0) { return ret; } written += ret; } } return 0; } // Read chunk from connection static int http_connection_read_chunk(http_connection_t *conn, void *buf, size_t max_len, size_t *actual_len) { int ret; *actual_len = 0; // First, read headers if not complete if (!conn->headers_complete) { unsigned char temp[1024]; if (conn->use_ssl) { ret = mbedtls_ssl_read(&conn->ssl, temp, sizeof(temp)); } else { ret = mbedtls_net_recv(&conn->server_fd, temp, sizeof(temp)); } if (ret > 0) { buffer_append(&conn->header_buf, temp, ret); if (parse_headers(conn)) { // Headers complete, check if there's body data in the buffer if (conn->header_buf.size > 0 && max_len > 0) { size_t to_copy = conn->header_buf.size > max_len ? max_len : conn->header_buf.size; memcpy(buf, conn->header_buf.data, to_copy); *actual_len = to_copy; conn->bytes_read += to_copy; // Remove copied data from buffer if (to_copy < conn->header_buf.size) { memmove(conn->header_buf.data, conn->header_buf.data + to_copy, conn->header_buf.size - to_copy); conn->header_buf.size -= to_copy; } else { conn->header_buf.size = 0; } } } } else if (ret < 0 && ret != MBEDTLS_ERR_SSL_WANT_READ) { return ret; } return 0; } // Headers are complete, read body data if (conn->chunked_encoding) { // Handle chunked encoding - simplified for now // In a full implementation, we'd need to parse chunk sizes if (conn->use_ssl) { ret = mbedtls_ssl_read(&conn->ssl, buf, max_len); } else { ret = mbedtls_net_recv(&conn->server_fd, buf, max_len); } } else { // Regular content-length based reading size_t remaining = conn->content_length - conn->bytes_read; if (remaining == 0) { return 0; // EOF } size_t to_read = remaining > max_len ? max_len : remaining; if (conn->use_ssl) { ret = mbedtls_ssl_read(&conn->ssl, buf, to_read); } else { ret = mbedtls_net_recv(&conn->server_fd, buf, to_read); } } if (ret > 0) { *actual_len = ret; conn->bytes_read += ret; } else if (ret == 0 || ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { return 0; // EOF } else if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { return 0; // Try again } return ret; } // JS function: fetch_start(url, options) - starts a chunked download static JSValue js_fetch_start(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1 || !JS_IsString(argv[0])) { return JS_ThrowTypeError(ctx, "fetch_start expects a URL string"); } const char *url = JS_ToCString(ctx, argv[0]); if (!url) { return JS_ThrowTypeError(ctx, "Invalid URL"); } char *host = NULL; char *port = NULL; char *path = NULL; int use_ssl = 0; buffer_t request_buf; JSValue result = JS_EXCEPTION; buffer_init(&request_buf); // Parse URL if (parse_url(url, &host, &port, &path, &use_ssl) < 0) { JS_FreeCString(ctx, url); return JS_ThrowTypeError(ctx, "Invalid URL format"); } // 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); 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); } js_free(ctx, tab); } } JS_FreeValue(ctx, headers); } buffer_append(&request_buf, "Connection: close\r\n\r\n", 21); // Create connection object http_connection_t *conn = js_mallocz(ctx, sizeof(http_connection_t)); if (!conn) { result = JS_ThrowOutOfMemory(ctx); goto cleanup; } http_connection_init(conn); // Start connection int ret = http_connection_start(conn, host, port, request_buf.data, request_buf.size, use_ssl); if (ret != 0) { char error_buf[256]; mbedtls_strerror(ret, error_buf, sizeof(error_buf)); http_connection_free(conn); js_free(ctx, conn); result = JS_ThrowInternalError(ctx, "Connection failed: %s", error_buf); goto cleanup; } // Create JS object to represent the connection result = JS_NewObjectClass(ctx, JS_CLASS_OBJECT); JS_SetOpaque(result, conn); // Add properties JS_DefinePropertyValueStr(ctx, result, "url", JS_NewString(ctx, url), JS_PROP_C_W_E); JS_DefinePropertyValueStr(ctx, result, "_connection", JS_NewInt64(ctx, (intptr_t)conn), JS_PROP_C_W_E); cleanup: JS_FreeCString(ctx, url); free(host); free(port); free(path); buffer_free(&request_buf); return result; } // JS function: fetch_read_chunk(connection, maxBytes) - reads a chunk static JSValue js_fetch_read_chunk(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "fetch_read_chunk expects a connection object"); } JSValue conn_val = JS_GetPropertyStr(ctx, argv[0], "_connection"); if (JS_IsUndefined(conn_val)) { return JS_ThrowTypeError(ctx, "Invalid connection object"); } int64_t conn_ptr; if (JS_ToInt64(ctx, &conn_ptr, conn_val) < 0) { JS_FreeValue(ctx, conn_val); return JS_ThrowTypeError(ctx, "Invalid connection pointer"); } JS_FreeValue(ctx, conn_val); http_connection_t *conn = (http_connection_t *)(intptr_t)conn_ptr; size_t max_bytes = 8192; if (argc >= 2) { int64_t mb; if (JS_ToInt64(ctx, &mb, argv[1]) == 0 && mb > 0) { max_bytes = mb; } } uint8_t *buf = js_malloc(ctx, max_bytes); if (!buf) { return JS_ThrowOutOfMemory(ctx); } size_t actual_len; int ret = http_connection_read_chunk(conn, buf, max_bytes, &actual_len); JSValue result; if (ret < 0 && ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { char error_buf[256]; mbedtls_strerror(ret, error_buf, sizeof(error_buf)); js_free(ctx, buf); return JS_ThrowInternalError(ctx, "Read failed: %s", error_buf); } if (actual_len == 0 && ret == 0) { // EOF or would block js_free(ctx, buf); return JS_NULL; } // Return chunk as ArrayBuffer result = JS_NewArrayBufferCopy(ctx, buf, actual_len); js_free(ctx, buf); return result; } // JS function: fetch_info(connection) - gets connection info static JSValue js_fetch_info(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "fetch_info expects a connection object"); } JSValue conn_val = JS_GetPropertyStr(ctx, argv[0], "_connection"); if (JS_IsUndefined(conn_val)) { return JS_ThrowTypeError(ctx, "Invalid connection object"); } int64_t conn_ptr; if (JS_ToInt64(ctx, &conn_ptr, conn_val) < 0) { JS_FreeValue(ctx, conn_val); return JS_ThrowTypeError(ctx, "Invalid connection pointer"); } JS_FreeValue(ctx, conn_val); http_connection_t *conn = (http_connection_t *)(intptr_t)conn_ptr; JSValue result = JS_NewObject(ctx); JS_DefinePropertyValueStr(ctx, result, "headers_complete", JS_NewBool(ctx, conn->headers_complete), JS_PROP_C_W_E); JS_DefinePropertyValueStr(ctx, result, "content_length", JS_NewInt64(ctx, conn->content_length), JS_PROP_C_W_E); JS_DefinePropertyValueStr(ctx, result, "bytes_read", JS_NewInt64(ctx, conn->bytes_read), JS_PROP_C_W_E); JS_DefinePropertyValueStr(ctx, result, "chunked_encoding", JS_NewBool(ctx, conn->chunked_encoding), JS_PROP_C_W_E); return result; } // JS function: fetch_close(connection) - closes the connection static JSValue js_fetch_close(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "fetch_close expects a connection object"); } JSValue conn_val = JS_GetPropertyStr(ctx, argv[0], "_connection"); if (JS_IsUndefined(conn_val)) { return JS_ThrowTypeError(ctx, "Invalid connection object"); } int64_t conn_ptr; if (JS_ToInt64(ctx, &conn_ptr, conn_val) < 0) { JS_FreeValue(ctx, conn_val); return JS_ThrowTypeError(ctx, "Invalid connection pointer"); } JS_FreeValue(ctx, conn_val); http_connection_t *conn = (http_connection_t *)(intptr_t)conn_ptr; http_connection_free(conn); js_free(ctx, conn); // Clear the connection pointer JS_DefinePropertyValueStr(ctx, argv[0], "_connection", JS_UNDEFINED, JS_PROP_C_W_E); return JS_UNDEFINED; } // 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"); } 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_ThrowTypeError(ctx, "Invalid URL format"); } // 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); 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); } js_free(ctx, tab); } } JS_FreeValue(ctx, headers); } 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; } #define countof(a) (sizeof(a)/sizeof(*(a))) // Module exports static const JSCFunctionListEntry js_http_funcs[] = { JS_CFUNC_DEF("fetch", 2, js_fetch), JS_CFUNC_DEF("fetch_start", 2, js_fetch_start), JS_CFUNC_DEF("fetch_read_chunk", 2, js_fetch_read_chunk), JS_CFUNC_DEF("fetch_info", 1, js_fetch_info), JS_CFUNC_DEF("fetch_close", 1, js_fetch_close), }; JSValue js_http_use(JSContext *js) { JSValue exp = JS_NewObject(js); JS_SetPropertyFunctionList(js, exp, js_http_funcs, countof(js_http_funcs)); return exp; }