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')
|
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 = cmake.subproject_options()
|
||||||
sdl3_opts.add_cmake_defines({
|
sdl3_opts.add_cmake_defines({
|
||||||
'SDL_STATIC': 'ON',
|
'SDL_STATIC': 'ON',
|
||||||
@@ -116,7 +132,6 @@ else
|
|||||||
deps += sdl3_proj.dependency('SDL3-static')
|
deps += sdl3_proj.dependency('SDL3-static')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
quickjs_opts = []
|
quickjs_opts = []
|
||||||
|
|
||||||
quickjs_opts += 'default_library=static'
|
quickjs_opts += 'default_library=static'
|
||||||
@@ -156,76 +171,6 @@ src += [
|
|||||||
'thirdparty/quirc/identify.c', 'thirdparty/quirc/version_db.c'
|
'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 = [
|
imsrc = [
|
||||||
'GraphEditor.cpp','ImCurveEdit.cpp','ImGradient.cpp','imgui_draw.cpp',
|
'GraphEditor.cpp','ImCurveEdit.cpp','ImGradient.cpp','imgui_draw.cpp',
|
||||||
'imgui_tables.cpp','imgui_widgets.cpp','imgui.cpp','ImGuizmo.cpp','imnodes.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.
|
- 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
|
return http
|
||||||
|
|||||||
@@ -1,269 +1,368 @@
|
|||||||
#include "quickjs.h"
|
#include "quickjs.h"
|
||||||
#include <curl/curl.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.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 {
|
typedef struct {
|
||||||
char url[512]; // URL for the request
|
char *data;
|
||||||
JSContext *ctx; // JS context for callbacks
|
size_t size;
|
||||||
JSValue callback; // Completion callback (optional)
|
size_t capacity;
|
||||||
JSValue on_data; // Streaming data callback (optional)
|
} buffer_t;
|
||||||
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;
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
static void buffer_init(buffer_t *buf) {
|
||||||
// Global data
|
buf->data = NULL;
|
||||||
// -----------------------------------------------------------------------------
|
buf->size = 0;
|
||||||
static CURLM *g_curl_multi = NULL; // CURL multi-handle for async I/O
|
buf->capacity = 0;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// 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 int buffer_append(buffer_t *buf, const void *data, size_t len) {
|
||||||
// JS function: http.fetch(url, options)
|
if (buf->size + len > buf->capacity) {
|
||||||
// - Enqueues an async HTTP request with optional streaming
|
size_t new_capacity = buf->capacity ? buf->capacity * 2 : 4096;
|
||||||
// -----------------------------------------------------------------------------
|
while (new_capacity < buf->size + len) {
|
||||||
static JSValue js_http_fetch(JSContext *ctx, JSValueConst this_val,
|
new_capacity *= 2;
|
||||||
int argc, JSValueConst *argv)
|
}
|
||||||
{
|
char *new_data = realloc(buf->data, new_capacity);
|
||||||
if (argc < 2 || !JS_IsString(argv[0])) {
|
if (!new_data) return -1;
|
||||||
return JS_ThrowTypeError(ctx, "fetch expects a URL string and an options object or callback");
|
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]);
|
const char *url = JS_ToCString(ctx, argv[0]);
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return JS_ThrowTypeError(ctx, "Invalid URL");
|
return JS_ThrowTypeError(ctx, "Invalid URL");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate request object
|
char *host = NULL;
|
||||||
HttpRequest *req = calloc(1, sizeof(*req));
|
char *port = NULL;
|
||||||
if (!req) {
|
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);
|
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;
|
// Build request
|
||||||
req->callback = JS_NULL;
|
buffer_append(&request_buf, "GET ", 4);
|
||||||
req->on_data = JS_NULL;
|
buffer_append(&request_buf, path, strlen(path));
|
||||||
|
buffer_append(&request_buf, " HTTP/1.1\r\n", 11);
|
||||||
// Parse second argument: callback or options object
|
buffer_append(&request_buf, "Host: ", 6);
|
||||||
if (JS_IsFunction(ctx, argv[1])) {
|
buffer_append(&request_buf, host, strlen(host));
|
||||||
req->callback = JS_DupValue(ctx, argv[1]);
|
buffer_append(&request_buf, "\r\n", 2);
|
||||||
} else if (JS_IsObject(argv[1])) {
|
|
||||||
JSValue callback = JS_GetPropertyStr(ctx, argv[1], "callback");
|
// Add headers from options if provided
|
||||||
JSValue on_data = JS_GetPropertyStr(ctx, argv[1], "on_data");
|
if (argc >= 2 && JS_IsObject(argv[1])) {
|
||||||
if (JS_IsFunction(ctx, callback)) {
|
JSValue headers = JS_GetPropertyStr(ctx, argv[1], "headers");
|
||||||
req->callback = JS_DupValue(ctx, callback);
|
if (JS_IsObject(headers)) {
|
||||||
}
|
JSPropertyEnum *tab;
|
||||||
if (JS_IsFunction(ctx, on_data)) {
|
uint32_t len;
|
||||||
req->on_data = JS_DupValue(ctx, on_data);
|
if (JS_GetOwnPropertyNames(ctx, &tab, &len, headers, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) == 0) {
|
||||||
}
|
for (uint32_t i = 0; i < len; i++) {
|
||||||
JS_FreeValue(ctx, callback);
|
JSValue key = JS_AtomToString(ctx, tab[i].atom);
|
||||||
JS_FreeValue(ctx, on_data);
|
JSValue val = JS_GetProperty(ctx, headers, tab[i].atom);
|
||||||
} 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);
|
|
||||||
|
|
||||||
if (ct) JS_SetPropertyStr(req->ctx, arg, "type", JS_NewString(req->ctx, ct));
|
const char *key_str = JS_ToCString(ctx, key);
|
||||||
} else {
|
const char *val_str = JS_ToCString(ctx, val);
|
||||||
JS_DefinePropertyValueStr(req->ctx, arg, "data", JS_NULL, JS_PROP_C_W_E);
|
|
||||||
const char *err_str = curl_easy_strerror(req->curl_result);
|
if (key_str && val_str) {
|
||||||
JS_DefinePropertyValueStr(req->ctx, arg, "error",
|
buffer_append(&request_buf, key_str, strlen(key_str));
|
||||||
JS_NewString(req->ctx, err_str ? err_str : "Unknown error"),
|
buffer_append(&request_buf, ": ", 2);
|
||||||
JS_PROP_C_W_E);
|
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_free(ctx, tab);
|
||||||
JS_FreeValue(req->ctx, arg);
|
|
||||||
JS_FreeValue(req->ctx, ret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
#define countof(a) (sizeof(a)/sizeof(*(a)))
|
||||||
// Module initialization
|
|
||||||
// -----------------------------------------------------------------------------
|
// Module exports
|
||||||
static const JSCFunctionListEntry js_http_funcs[] = {
|
static const JSCFunctionListEntry js_http_funcs[] = {
|
||||||
JS_CFUNC_DEF("fetch", 2, js_http_fetch),
|
JS_CFUNC_DEF("fetch", 2, js_fetch),
|
||||||
JS_CFUNC_DEF("poll", 0, js_http_poll),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
JSValue js_http_use(JSContext *ctx)
|
JSValue js_http_use(JSContext *js)
|
||||||
{
|
{
|
||||||
// Initialize CURL globally (once per process)
|
JSValue exp = JS_NewObject(js);
|
||||||
static int s_curl_init_done = 0;
|
JS_SetPropertyFunctionList(js, exp, js_http_funcs, countof(js_http_funcs));
|
||||||
if (!s_curl_init_done) {
|
return exp;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,6 +193,32 @@ JSC_CCALL(os_on,
|
|||||||
rt->on_exception = JS_DupValue(js,argv[1]);
|
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) \
|
#define JSOBJ_ADD_FIELD(OBJ, STRUCT, FIELD, TYPE) \
|
||||||
JS_SetPropertyStr(js, OBJ, #FIELD, TYPE##2js(js,STRUCT.FIELD));\
|
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, on, 2),
|
||||||
MIST_FUNC_DEF(os, rusage, 0),
|
MIST_FUNC_DEF(os, rusage, 0),
|
||||||
MIST_FUNC_DEF(os, mallinfo, 0),
|
MIST_FUNC_DEF(os, mallinfo, 0),
|
||||||
|
MIST_FUNC_DEF(os, buffer2string, 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
JSValue js_os_use(JSContext *js) {
|
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