remove curl and openssl dependencies
Some checks failed
Build and Deploy / build-macos (push) Failing after 5s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / build-linux (push) Successful in 1m44s
Build and Deploy / package-dist (push) Has been skipped
Build and Deploy / deploy-itch (push) Has been skipped
Build and Deploy / deploy-gitea (push) Has been skipped
Some checks failed
Build and Deploy / build-macos (push) Failing after 5s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / build-linux (push) Successful in 1m44s
Build and Deploy / package-dist (push) Has been skipped
Build and Deploy / deploy-itch (push) Has been skipped
Build and Deploy / deploy-gitea (push) Has been skipped
This commit is contained in:
87
meson.build
87
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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,269 +1,368 @@
|
||||
#include "quickjs.h"
|
||||
#include <curl/curl.h>
|
||||
#include <stdlib.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>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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;
|
||||
static void buffer_init(buffer_t *buf) {
|
||||
buf->data = NULL;
|
||||
buf->size = 0;
|
||||
buf->capacity = 0;
|
||||
}
|
||||
|
||||
// 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
|
||||
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;
|
||||
}
|
||||
req->response = ptr;
|
||||
memcpy(&(req->response[req->size]), contents, realsize);
|
||||
req->size += realsize;
|
||||
req->response[req->size] = '\0'; // Null-terminate
|
||||
return realsize;
|
||||
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;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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 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");
|
||||
}
|
||||
|
||||
// Get URL
|
||||
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) {
|
||||
JS_FreeCString(ctx, url);
|
||||
return JS_ThrowInternalError(ctx, "Failed to allocate memory");
|
||||
}
|
||||
strncpy(req->url, url, sizeof(req->url) - 1);
|
||||
req->ctx = ctx;
|
||||
req->callback = JS_NULL;
|
||||
req->on_data = JS_NULL;
|
||||
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;
|
||||
|
||||
// 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);
|
||||
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");
|
||||
}
|
||||
if (JS_IsFunction(ctx, on_data)) {
|
||||
req->on_data = JS_DupValue(ctx, on_data);
|
||||
|
||||
// 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_FreeValue(ctx, callback);
|
||||
JS_FreeValue(ctx, on_data);
|
||||
|
||||
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 {
|
||||
JS_FreeCString(ctx, url);
|
||||
free(req);
|
||||
return JS_ThrowTypeError(ctx, "Second argument must be a callback or options object");
|
||||
ret = http_request(host, port, request_buf.data, request_buf.size, &response_buf);
|
||||
}
|
||||
|
||||
JS_FreeCString(ctx, url);
|
||||
if (ret == 0 && response_buf.data) {
|
||||
// Extract body
|
||||
size_t body_len;
|
||||
char *body = extract_body(response_buf.data, response_buf.size, &body_len);
|
||||
|
||||
// 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);
|
||||
|
||||
if (ct) JS_SetPropertyStr(req->ctx, arg, "type", JS_NewString(req->ctx, ct));
|
||||
if (body) {
|
||||
// Return body as ArrayBuffer
|
||||
result = JS_NewArrayBufferCopy(ctx, (uint8_t *)body, body_len);
|
||||
} 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);
|
||||
result = JS_ThrowInternalError(ctx, "Failed to parse HTTP response");
|
||||
}
|
||||
JSValue ret = JS_Call(req->ctx, req->callback, JS_UNDEFINED, 1, &arg);
|
||||
JS_FreeValue(req->ctx, arg);
|
||||
JS_FreeValue(req->ctx, ret);
|
||||
} else {
|
||||
char error_buf[256];
|
||||
mbedtls_strerror(ret, error_buf, sizeof(error_buf));
|
||||
result = JS_ThrowInternalError(ctx, "Request failed: %s", error_buf);
|
||||
}
|
||||
|
||||
// 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_FreeCString(ctx, url);
|
||||
free(host);
|
||||
free(port);
|
||||
free(path);
|
||||
buffer_free(&request_buf);
|
||||
buffer_free(&response_buf);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
#define countof(a) (sizeof(a)/sizeof(*(a)))
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Module initialization
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
4
subprojects/mbedtls.wrap
Normal file
4
subprojects/mbedtls.wrap
Normal file
@@ -0,0 +1,4 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/Mbed-TLS/mbedtls.git
|
||||
revision = v3.6.3.1
|
||||
depth = 1
|
||||
6
tests/https.js
Normal file
6
tests/https.js
Normal file
@@ -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))
|
||||
Reference in New Issue
Block a user