commit 7e252316edd93ec69f970a4b7dfb553f54c2ee10
parent 2aa58df6d85a468fffa07e1a45eac3b2912774dd
Author: Florian Dold <florian@dold.me>
Date: Fri, 11 Nov 2022 20:18:15 +0100
wip
Diffstat:
7 files changed, 248 insertions(+), 5 deletions(-)
diff --git a/Makefile b/Makefile
@@ -174,8 +174,8 @@ QJS_LIB_OBJS+=$(OBJDIR)/libbf.o
QJS_OBJS+=$(OBJDIR)/qjscalc.o
endif
-HOST_LIBS=-lm -ldl -lpthread
-LIBS=-lm
+HOST_LIBS=-lm -ldl -lpthread -lcurl
+LIBS=-lm -lcurl
ifndef CONFIG_WIN32
LIBS+=-ldl -lpthread
endif
diff --git a/prelude.js b/prelude.js
@@ -8,13 +8,17 @@ class TextEncoder {
class TextDecoder {
decode(bytes) {
- return _decodeUtf8(bytes.buffer);
+ if (ArrayBuffer.isView(bytes)) {
+ return _decodeUtf8(bytes.buffer);
+ }
+ return _decodeUtf8(bytes);
}
}
globalThis.TextEncoder = TextEncoder;
globalThis.TextDecoder = TextDecoder;
globalThis.setTimeout = (f, t) => os.setTimeout(f, t);
+globalThis.clearTimeout = (h) => os.clearTimeout(h);
globalThis.setImmediate = (f) => os.setTimeout(f, 0);
// FIXME: log to the right streams!
diff --git a/qjsc.c b/qjsc.c
@@ -452,6 +452,8 @@ static int output_executable(const char *out_filename, const char *cfilename,
*arg++ = "-lm";
*arg++ = "-ldl";
*arg++ = "-lpthread";
+ // FIXME: Make conditional
+ *arg++ = "-lcurl";
*arg = NULL;
if (verbose) {
diff --git a/quickjs-libc.c b/quickjs-libc.c
@@ -75,6 +75,8 @@ typedef sig_t sighandler_t;
- add socket calls
*/
+#include <curl/curl.h>
+
typedef struct {
struct list_head link;
int fd;
@@ -2015,6 +2017,202 @@ static void js_os_timer_mark(JSRuntime *rt, JSValueConst val,
}
}
+typedef struct {
+ DynBuf response_data;
+ JSValue headers_list;
+ JSContext *ctx;
+ uint8_t *readbuf;
+ size_t readpos;
+ size_t readlen;
+} CurlRequestContext;
+
+static size_t curl_header_callback(char *buffer, size_t size,
+ size_t nitems, void *userdata)
+{
+ CurlRequestContext *rctx = userdata;
+ size_t sz = size * nitems;
+
+ qjs_array_append_new(rctx->ctx, rctx->headers_list, JS_NewStringLen(rctx->ctx, buffer, sz));
+
+ return sz;
+}
+
+static size_t curl_write_cb(void *data, size_t size, size_t nmemb, void *userp)
+{
+ size_t realsize = size * nmemb;
+ CurlRequestContext *rctx = userp;
+
+ if (0 != dbuf_put(&rctx->response_data, data, realsize)) {
+ return 0;
+ }
+
+ return realsize;
+}
+
+size_t read_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+ CurlRequestContext *rctx = userdata;
+ ssize_t src_available = rctx->readlen - rctx->readpos;
+ size_t dst_available = size * nmemb;
+ size_t n;
+
+ if (!rctx->readbuf) {
+ return 0;
+ }
+ if (src_available <= 0) {
+ return 0;
+ }
+ n = src_available;
+ if (dst_available < src_available) {
+ n = dst_available;
+ }
+ memcpy(ptr, rctx->readbuf + rctx->readpos, n);
+ rctx->readpos += n;
+ return n;
+}
+
+int expect_property_str_bool(JSContext *ctx, JSValueConst this_val, const char *prop_name)
+{
+ JSValue prop_val;
+ BOOL bool_val;
+
+ prop_val = JS_GetPropertyStr(ctx, this_val, prop_name);
+ if (JS_IsException(prop_val)) {
+ return -1;
+ }
+ bool_val = JS_ToBool(ctx, prop_val);
+ JS_FreeValue(ctx, prop_val);
+ return bool_val;
+}
+
+/**
+ * fetchHttp(url, { method, headers, body })
+ */
+static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ const char *req_url = NULL;
+ JSValue ret_val = JS_UNDEFINED;
+ JSValue options = JS_UNINITIALIZED;
+ JSValue method = JS_UNINITIALIZED;
+ const char *method_str = NULL;
+ CURLcode res;
+ CURL *curl = NULL;
+ CurlRequestContext req_context = { 0 };
+ struct curl_slist *headers = NULL;
+ int debug = FALSE;
+
+ req_context.ctx = ctx;
+ req_context.headers_list = JS_NewArray(ctx);
+ dbuf_init(&req_context.response_data);
+
+ req_url = JS_ToCString(ctx, argv[0]);
+ if (!req_url) {
+ goto exception;
+ }
+
+ options = argv[1];
+ if (JS_VALUE_GET_TAG (options) == JS_TAG_UNDEFINED) {
+ method = JS_NewString(ctx, "get");
+ } else if (JS_VALUE_GET_TAG(options) == JS_TAG_OBJECT) {
+ method = JS_GetPropertyStr(ctx, options, "method");
+ debug = expect_property_str_bool (ctx, options, "debug");
+ } else {
+ JS_ThrowTypeError(ctx, "invalid options");
+ goto exception;
+ }
+
+ curl = curl_easy_init();
+ if (!curl) {
+ JS_ThrowInternalError(ctx, "unable to init libcurl");
+ goto exception;
+ }
+ curl_easy_setopt(curl, CURLOPT_URL, req_url);
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_callback);
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, &req_context);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &req_context);
+ if (debug == TRUE) {
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+ }
+
+ method_str = JS_ToCString(ctx, method);
+
+ if (0 == strcasecmp(method_str, "get")) {
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
+ headers = curl_slist_append (headers, "Accept: application/json");
+ } else if (0 == strcasecmp(method_str, "post")) {
+ JSValue data;
+ uint8_t *data_ptr;
+ size_t data_len;
+
+ data = JS_GetPropertyStr(ctx, options, "data");
+ if (JS_IsException(data)) {
+ goto exception;
+ }
+ data_ptr = JS_GetArrayBuffer(ctx, &data_len, data);
+ if (!data_ptr) {
+ goto exception;
+ }
+ req_context.readbuf = malloc(data_len);
+ memcpy(req_context.readbuf, data_ptr, data_len);
+ req_context.readlen = data_len;
+ JS_FreeValue(ctx, data);
+ curl_easy_setopt(curl, CURLOPT_POST, 1L);
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);
+ curl_easy_setopt(curl, CURLOPT_READDATA, &req_context);
+ headers = curl_slist_append (headers, "Content-Type: application/json");
+ } else {
+ JS_ThrowTypeError(ctx, "invalid request method");
+ goto exception;
+ }
+
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+
+ res = curl_easy_perform(curl);
+ if (CURLE_OK != res) {
+ JS_ThrowInternalError(ctx, "fetch failed");
+ goto exception;
+ }
+ ret_val = JS_NewObject(ctx);
+ if (JS_IsException(ret_val)) {
+ goto exception;
+ }
+ {
+ long resp_code;
+
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &resp_code);
+ JS_SetPropertyStr(ctx, ret_val, "status", JS_NewInt32(ctx, resp_code));
+ JS_SetPropertyStr(ctx, ret_val, "headers", req_context.headers_list);
+ req_context.headers_list = JS_UNINITIALIZED;
+ // FIXME: Don't copy, own buffer
+ JS_SetPropertyStr(ctx,
+ ret_val,
+ "data",
+ JS_NewArrayBufferCopy(ctx,
+ req_context.response_data.buf,
+ req_context.response_data.size));
+ }
+done:
+ if (curl) {
+ curl_easy_cleanup(curl);
+ curl = NULL;
+ }
+ if (NULL != req_context.readbuf) {
+ free(req_context.readbuf);
+ }
+ JS_FreeCString(ctx, method_str);
+ dbuf_free(&req_context.response_data);
+ JS_FreeValue(ctx, method);
+ JS_FreeValue(ctx, req_context.headers_list);
+ JS_FreeCString(ctx, req_url);
+ curl_slist_free_all(headers);
+ return ret_val;
+exception:
+ ret_val = JS_EXCEPTION;
+ goto done;
+}
+
static JSValue js_os_setTimeout(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
@@ -3622,6 +3820,7 @@ static const JSCFunctionListEntry js_os_funcs[] = {
OS_FLAG(SIGTTOU),
#endif
JS_CFUNC_DEF("setTimeout", 2, js_os_setTimeout ),
+ JS_CFUNC_DEF("fetchHttp", 2, js_os_fetchHttp ),
JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ),
JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ),
JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ),
@@ -3667,6 +3866,11 @@ static int js_os_init(JSContext *ctx, JSModuleDef *m)
JS_NewClassID(&js_os_timer_class_id);
JS_NewClass(JS_GetRuntime(ctx), js_os_timer_class_id, &js_os_timer_class);
+ if (CURLE_OK != curl_global_init (CURL_GLOBAL_DEFAULT)) {
+ JS_ThrowInternalError(ctx, "unable to init libcurl (global)");
+ return -1;
+ }
+
#ifdef USE_WORKER
{
JSRuntime *rt = JS_GetRuntime(ctx);
@@ -3738,10 +3942,11 @@ static JSValue js_encode_utf8(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
const char *str;
+ size_t len;
JSValue buf;
- str = JS_ToCString(ctx, argv[0]);
+ str = JS_ToCStringLen2(ctx, &len, argv[0], FALSE);
// FIXME: Don't copy buffer but pass destructor function
- buf = JS_NewArrayBufferCopy(ctx, (const uint8_t*) str, strlen(str));
+ buf = JS_NewArrayBufferCopy(ctx, (const uint8_t*) str, len);
JS_FreeCString(ctx, str);
return buf;
}
diff --git a/quickjs.c b/quickjs.c
@@ -39011,6 +39011,33 @@ static JSValue js_array_pop(JSContext *ctx, JSValueConst this_val,
return JS_EXCEPTION;
}
+int qjs_array_append_new(JSContext *ctx, JSValue this_val, JSValue item)
+{
+ JSValue obj;
+ int64_t len, from, newLen;
+
+ obj = JS_ToObject(ctx, this_val);
+ if (js_get_length64(ctx, &len, obj))
+ goto exception;
+ newLen = len + 1;
+ if (newLen > MAX_SAFE_INTEGER) {
+ JS_ThrowTypeError(ctx, "Array loo long");
+ goto exception;
+ }
+ from = len;
+ if (JS_SetPropertyInt64(ctx, obj, from, item) < 0)
+ goto exception;
+ if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, newLen)) < 0)
+ goto exception;
+
+ JS_FreeValue(ctx, obj);
+ return 0;
+
+ exception:
+ JS_FreeValue(ctx, obj);
+ return -1;
+}
+
static JSValue js_array_push(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int unshift)
{
diff --git a/quickjs.h b/quickjs.h
@@ -708,6 +708,9 @@ static inline const char *JS_ToCString(JSContext *ctx, JSValueConst val1)
}
void JS_FreeCString(JSContext *ctx, const char *ptr);
+
+int qjs_array_append_new (JSContext *ctx, JSValue array, JSValue item);
+
JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValueConst proto, JSClassID class_id);
JSValue JS_NewObjectClass(JSContext *ctx, int class_id);
JSValue JS_NewObjectProto(JSContext *ctx, JSValueConst proto);
diff --git a/repl.js b/repl.js
@@ -45,6 +45,8 @@ import * as os from "os";
var config_numcalc = (typeof os.open === "undefined");
var has_jscalc = (typeof Fraction === "function");
var has_bignum = (typeof BigFloat === "function");
+
+ var use_history_file = true;
var colors = {
none: "\x1b[0m",