Files
cell/source/qjs_http.c

836 lines
27 KiB
C

#include "quickjs.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <mbedtls/net_sockets.h>
#include <mbedtls/ssl.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/error.h>
#include <ctype.h>
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;
}