summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-02-23 15:54:29 +0100
committerFlorian Dold <florian@dold.me>2024-02-26 21:06:41 +0100
commit47fc98540143afee8f77c6298fdeb3c0b0d7b6ec (patch)
tree868d9b582cd9745c30161f5e6753c945d8d49333
parent0595d6b972a4c3a9f26c93ced206a82a403146de (diff)
downloadquickjs-tart-dev/dold/native-http.tar.gz
quickjs-tart-dev/dold/native-http.tar.bz2
quickjs-tart-dev/dold/native-http.zip
native http lib interfacedev/dold/native-http
-rw-r--r--meson.build2
-rw-r--r--qtart.c13
-rw-r--r--quickjs/quickjs-http.c497
-rw-r--r--quickjs/quickjs-http.h213
-rw-r--r--quickjs/quickjs-libc.c753
-rw-r--r--quickjs/quickjs-libc.h8
-rw-r--r--quickjs/quickjs.c11
-rw-r--r--quickjs/quickjs.h1
-rw-r--r--taler_wallet_core_lib.c17
-rw-r--r--taler_wallet_core_lib.h13
-rw-r--r--taler_wallet_core_lib_preprocessed.h381
-rw-r--r--xcode/FTalerWalletcore-Bridging-Header.h2
12 files changed, 1578 insertions, 333 deletions
diff --git a/meson.build b/meson.build
index e784384..43bd182 100644
--- a/meson.build
+++ b/meson.build
@@ -59,7 +59,7 @@ libunicode = static_library('unicode', 'quickjs/libunicode.c')
# general utilities
cutils = static_library('cutils', 'quickjs/cutils.c')
# standard library for quickjs (std and os modules)
-quickjs_libc = static_library('quickjs-libc', 'quickjs/quickjs-libc.c', dependencies : curl_dep )
+quickjs_libc = static_library('quickjs-libc', ['quickjs/quickjs-libc.c', 'quickjs/quickjs-http.c'], dependencies : curl_dep )
# base JS interpreter
quickjs = static_library('quickjs', 'quickjs/quickjs.c')
sqlite3 = static_library('sqlite3', 'sqlite3/sqlite3.c')
diff --git a/qtart.c b/qtart.c
index cabde5a..0d2ab65 100644
--- a/qtart.c
+++ b/qtart.c
@@ -40,6 +40,7 @@
#endif
#include "quickjs/cutils.h"
+#include "quickjs/quickjs-http.h"
#include "tart_module.h"
@@ -326,6 +327,7 @@ int main(int argc, char **argv)
{
JSRuntime *rt;
JSContext *ctx;
+ struct JSHttpClientImplementation *http_impl = NULL;
struct trace_malloc_data trace_data = { NULL };
int optind;
char *expr = NULL;
@@ -459,11 +461,17 @@ int main(int argc, char **argv)
js_std_set_worker_new_context_func(JS_NewCustomContext);
js_std_init_handlers(rt);
ctx = JS_NewCustomContext(rt);
- js_os_set_host_message_handler(ctx, handle_host_message, NULL);
if (!ctx) {
fprintf(stderr, "qjs: cannot allocate JS context\n");
exit(2);
}
+ js_os_set_host_message_handler(ctx, handle_host_message, NULL);
+ http_impl = js_curl_http_client_create();
+ if (!http_impl) {
+ fprintf(stderr, "qjs: cannot create HTTP client implementation\n");
+ exit(2);
+ }
+ js_os_set_http_impl(rt, http_impl);
/* loader for ES6 modules */
JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);
@@ -518,6 +526,8 @@ int main(int argc, char **argv)
}
js_std_loop(ctx);
}
+
+ fprintf(stderr, "done with main loop\n");
if (dump_memory) {
JSMemoryUsage stats;
@@ -525,6 +535,7 @@ int main(int argc, char **argv)
JS_DumpMemoryUsage(stdout, &stats, rt);
}
js_std_free_handlers(rt);
+ js_curl_http_client_destroy(http_impl);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
diff --git a/quickjs/quickjs-http.c b/quickjs/quickjs-http.c
new file mode 100644
index 0000000..9fb1fa2
--- /dev/null
+++ b/quickjs/quickjs-http.c
@@ -0,0 +1,497 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2024 Taler Systems SA
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include <stdlib.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <curl/curl.h>
+#include <arpa/inet.h>
+#include <strings.h>
+#include <string.h>
+#include <assert.h>
+
+#include "cutils.h"
+#include "quickjs-http.h"
+#include "list.h"
+
+struct CurlClientState {
+ pthread_t thread;
+ pthread_mutex_t mutex;
+ BOOL started;
+ BOOL stopped;
+ CURLSH *curlsh;
+ CURLM *curlm;
+ int last_request_id;
+ struct list_head request_list; /* list of CurlRequestState.link */
+};
+
+struct CurlRequestState {
+ struct CurlClientState *ccs;
+ struct list_head link;
+ DynBuf response_data;
+ BOOL cancelled;
+ CURL *curl;
+ int request_id;
+ enum JSHttpRedirectFlag redirect;
+ JSHttpResponseCb response_cb;
+ void *response_cb_cls;
+ // Request headers
+ struct curl_slist *req_headers;
+ struct curl_slist *resp_headers;
+ char *errbuf;
+};
+
+// Must only be called with locked client mutex
+static void destroy_curl_request_state(struct CurlRequestState *crs)
+{
+ struct CurlClientState *ccs;
+
+ if (!crs) {
+ return;
+ }
+ ccs = crs->ccs;
+ crs->ccs = NULL;
+
+ list_del(&crs->link);
+ curl_slist_free_all(crs->req_headers);
+ curl_slist_free_all(crs->resp_headers);
+ dbuf_free(&crs->response_data);
+ if (crs->curl) {
+ curl_easy_cleanup(crs->curl);
+ crs->curl = NULL;
+ }
+ free(crs->errbuf);
+ free(crs);
+}
+
+static void *
+handle_done(CURL *curl, CURLcode res)
+{
+ struct CurlRequestState *crs = NULL;
+ struct CurlClientState *ccs = NULL;
+ struct JSHttpResponseInfo hri = { 0 };
+ long resp_code;
+ char **headers = NULL;
+ BOOL cancelled;
+
+ curl_easy_getinfo(curl, CURLINFO_PRIVATE, &crs);
+ ccs = crs->ccs;
+
+ hri.request_id = crs->request_id;
+
+ if (CURLE_OK == res) {
+ int num_headers = 0;
+ int i;
+ char **headers;
+ struct curl_slist *sl = crs->resp_headers;
+ char *url = NULL;
+
+ curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &url);
+
+ if (crs->redirect == JS_HTTP_REDIRECT_ERROR && NULL != url) {
+ hri.status = 0;
+ hri.errmsg = crs->errbuf;
+ strncpy(crs->errbuf, "Got redirect status, but redirects are not allowed for this request", CURL_ERROR_SIZE);
+ goto done;
+ }
+
+ while (sl != NULL) {
+ if (NULL != strchr(sl->data, ':')) {
+ num_headers++;
+ }
+ sl = sl->next;
+ }
+
+ headers = malloc((num_headers + 1) * sizeof(char *));
+ if (!headers) {
+ hri.status = 0;
+ goto done;
+ }
+ memset(headers, 0, (num_headers + 1) * sizeof (char *));
+ sl = crs->resp_headers;
+ i = 0;
+ while (sl != NULL) {
+ if (NULL != strchr(sl->data, ':')) {
+ headers[i] = sl->data;
+ i++;
+ }
+ sl = sl->next;
+ }
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &resp_code);
+ hri.status = resp_code;
+ hri.body = crs->response_data.buf;
+ hri.body_len = crs->response_data.size;
+ hri.response_headers = headers;
+ } else {
+ hri.status = 0;
+ hri.errmsg = crs->errbuf;
+ }
+
+done:
+
+ pthread_mutex_lock(&ccs->mutex);
+ cancelled = crs->cancelled;
+ pthread_mutex_unlock(&ccs->mutex);
+
+ if (cancelled == FALSE) {
+ // FIXME: What if this CB somehow destroys the client?
+ crs->response_cb(crs->response_cb_cls, &hri);
+ }
+
+ if (NULL != headers) {
+ for (char **h=headers; *h; h++) {
+ free(*h);
+ }
+ free(headers);
+ }
+ pthread_mutex_lock(&ccs->mutex);
+ destroy_curl_request_state(crs);
+ pthread_mutex_unlock(&ccs->mutex);
+ return NULL;
+}
+
+static size_t curl_header_callback(char *buffer, size_t size,
+ size_t nitems, void *userdata)
+{
+ struct CurlRequestState *crs = userdata;
+ size_t sz = size * nitems;
+ char *hval;
+
+ hval = strndup(buffer, sz);
+ if (!hval) {
+ return 0;
+ }
+ crs->resp_headers = curl_slist_append(crs->resp_headers, hval);
+ free(hval);
+ return sz;
+}
+
+
+static size_t curl_write_cb(void *data, size_t size, size_t nmemb, void *userp)
+{
+ size_t realsize = size * nmemb;
+ struct CurlRequestState *rctx = userp;
+
+ if (0 != dbuf_put(&rctx->response_data, data, realsize)) {
+ return 0;
+ }
+
+ return realsize;
+}
+
+
+static int
+create_impl(void *cls, struct JSHttpRequestInfo *req_info)
+{
+ struct CurlClientState *ccs = cls;
+ struct CurlRequestState *crs;
+ pthread_t thread;
+ int res;
+ CURL *curl;
+ BOOL debug = req_info->debug > 0;
+ const char *method = req_info->method;
+
+ crs = malloc(sizeof *crs);
+ if (!crs) {
+ return -1;
+ }
+ memset(crs, 0, sizeof *crs);
+ crs->request_id = ++ccs->last_request_id;
+ crs->ccs = ccs;
+ crs->response_cb = req_info->response_cb;
+ crs->response_cb_cls = req_info->response_cb_cls;
+ crs->errbuf = malloc(CURL_ERROR_SIZE);
+ if (!crs->errbuf) {
+ goto error;
+ }
+ memset(crs->errbuf, 0, CURL_ERROR_SIZE);
+ dbuf_init(&crs->response_data);
+
+ curl = curl_easy_init();
+ crs->curl = curl;
+ curl_easy_setopt(curl, CURLOPT_PRIVATE, crs);
+ curl_easy_setopt(curl, CURLOPT_SHARE, ccs->curlsh);
+ curl_easy_setopt(curl, CURLOPT_URL, req_info->url);
+ curl_easy_setopt(curl, CURLOPT_DNS_SERVERS, "9.9.9.9");
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, "qtart");
+ curl_easy_setopt(curl, CURLOPT_CAINFO, "/etc/ssl/certs/ca-certificates.crt");
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_callback);
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, crs);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, crs);
+
+ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, crs->errbuf);
+
+ // FIXME: This is only a temporary hack until we have proper TLS CA support
+ // on all platforms
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
+
+ if (req_info->timeout_ms < 0) {
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 0L);
+ } else if (0 == req_info->timeout_ms) {
+ // Default timeout of 5 minutes.
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 5L * 60000L);
+ } else {
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, (long) req_info->timeout_ms);
+ }
+
+ if (debug == TRUE) {
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+ }
+
+ crs->redirect = req_info->redirect;
+
+ switch (req_info->redirect) {
+ case JS_HTTP_REDIRECT_TRANSPARENT:
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+ break;
+ case JS_HTTP_REDIRECT_MANUAL:
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
+ break;
+ case JS_HTTP_REDIRECT_ERROR:
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
+ break;
+ default:
+ assert(0);
+ }
+
+ if (0 == strcasecmp(req_info->method, "get")) {
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
+ } else if (0 == strcasecmp(method, "delete")) {
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
+ } else if (0 == strcasecmp(method, "head")) {
+ curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
+ } else if ((0 == strcasecmp(method, "post")) ||
+ (0 == strcasecmp(method, "put"))) {
+ curl_easy_setopt(curl, CURLOPT_POST, 1L);
+ if (0 == strcasecmp(method, "put")) {
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
+ }
+ if (req_info->req_body_len > 0) {
+ curl_off_t len = req_info->req_body_len;
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, len);
+ curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, req_info->req_body);
+ }
+ } else {
+ goto error;
+ }
+
+ if (req_info->request_headers != NULL) {
+ char **h = req_info->request_headers;
+ while (*h) {
+ crs->req_headers = curl_slist_append(crs->req_headers, *h);
+ h++;
+ }
+ }
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, crs->req_headers);
+
+ pthread_mutex_lock(&ccs->mutex);
+ list_add_tail(&crs->link, &ccs->request_list);
+ pthread_mutex_unlock(&ccs->mutex);
+
+ curl_multi_add_handle(ccs->curlm, curl);
+ curl_multi_wakeup(ccs->curlm);
+
+ return crs->request_id;
+error:
+ if (crs) {
+ dbuf_free(&crs->response_data);
+ if (crs->errbuf) {
+ free(crs->errbuf);
+ }
+ if (crs->curl) {
+ curl_easy_cleanup(crs->curl);
+ }
+ free(crs);
+ }
+ return -1;
+}
+
+static int
+destroy_impl(void *cls, int request_id)
+{
+ struct list_head *el;
+ struct CurlClientState *ccs = cls;
+
+ pthread_mutex_lock(&ccs->mutex);
+
+ list_for_each(el, &ccs->request_list) {
+ struct CurlRequestState *crs = list_entry(el, struct CurlRequestState, link);
+ if (crs->request_id == request_id) {
+ crs->cancelled = TRUE;
+ }
+ }
+
+ pthread_mutex_unlock(&ccs->mutex);
+
+ return 0;
+}
+
+/**
+ * Entry point for the thread that processes HTTP requests with libcurl.
+ */
+static void *
+curl_multi_thread_run(void *cls)
+{
+ struct CurlClientState *ccs = cls;
+ int still_running;
+ struct CURLMsg *m;
+ BOOL stopped;
+
+ while (1) {
+ CURLMcode mc;
+
+ mc = curl_multi_perform(ccs->curlm, &still_running);
+
+ if (CURLM_OK != mc) {
+ fprintf(stderr, "curl_multi_perform failed\n");
+ break;
+ }
+
+ mc = curl_multi_poll(ccs->curlm, NULL, 0, 1000, NULL);
+ if (CURLM_OK != mc) {
+ fprintf(stderr, "curl_multi_poll failed\n");
+ break;
+ }
+
+ pthread_mutex_lock(&ccs->mutex);
+ stopped = ccs->stopped;
+ pthread_mutex_unlock(&ccs->mutex);
+
+ if (stopped) {
+ break;
+ }
+
+ do {
+ int msgq = 0;
+ m = curl_multi_info_read(ccs->curlm, &msgq);
+ if (m && (m->msg == CURLMSG_DONE)) {
+ CURL *e = m->easy_handle;
+ curl_multi_remove_handle(ccs->curlm, e);
+ handle_done(e, m->data.result);
+ }
+ } while(m);
+ }
+ if (CURLM_OK != curl_multi_cleanup(ccs->curlm)) {
+ fprintf(stderr, "warning: curl_multi_cleanup failed\n");
+ }
+ if (CURLSHE_OK != curl_share_cleanup(ccs->curlsh)) {
+ fprintf(stderr, "warning: curl_share_cleanup failed\n");
+ }
+}
+
+struct JSHttpClientImplementation *
+js_curl_http_client_create()
+{
+ struct JSHttpClientImplementation *impl = NULL;
+ struct CurlClientState *ccs = NULL;
+ int res;
+
+ ccs = malloc(sizeof *ccs);
+ if (!ccs) {
+ goto error;
+ }
+
+ pthread_mutex_init(&ccs->mutex, NULL);
+ ccs->started = FALSE;
+ ccs->stopped = FALSE;
+ ccs->last_request_id = 0;
+ ccs->curlsh = curl_share_init();
+ if (!ccs->curlsh) {
+ goto error;
+ }
+ ccs->curlm = curl_multi_init();
+ if (!ccs->curlm) {
+ goto error;
+ }
+ init_list_head(&ccs->request_list);
+
+ curl_share_setopt(ccs->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
+ curl_share_setopt(ccs->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
+ curl_share_setopt(ccs->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
+
+ impl = malloc(sizeof *impl);
+ if (!impl) {
+ goto error;
+ }
+ impl->req_create = &create_impl;
+ impl->req_cancel = &destroy_impl;
+ impl->cls = ccs;
+
+ res = pthread_create(&ccs->thread, NULL, &curl_multi_thread_run, ccs);
+ ccs->started = TRUE;
+
+ if (0 != res) {
+ goto error;
+ }
+
+ return impl;
+error:
+ if (ccs) {
+ curl_share_cleanup(ccs->curlsh);
+ curl_multi_cleanup(ccs->curlm);
+ free(ccs);
+ }
+ if (impl) {
+ free(impl);
+ }
+ return NULL;
+}
+
+static void
+destroy_client_state(struct CurlClientState *ccs)
+{
+ struct list_head *el, *el1;
+ if (!ccs) {
+ return;
+ }
+ if (ccs->started == TRUE) {
+ void *retval;
+ int res;
+
+ pthread_mutex_lock(&ccs->mutex);
+ ccs->stopped = TRUE;
+ pthread_mutex_unlock(&ccs->mutex);
+ curl_multi_wakeup(ccs->curlm);
+ res = pthread_join(ccs->thread, &retval);
+ if (0 != res) {
+ fprintf(stderr, "warning: could not join with curl thread\n");
+ }
+ ccs->started = FALSE;
+ }
+ pthread_mutex_lock(&ccs->mutex);
+ list_for_each_safe(el, el1, &ccs->request_list) {
+ struct CurlRequestState *crs = list_entry(el, struct CurlRequestState, link);
+ destroy_curl_request_state(crs);
+ }
+ pthread_mutex_unlock(&ccs->mutex);
+ pthread_mutex_destroy(&ccs->mutex);
+ free(ccs);
+}
+
+void
+js_curl_http_client_destroy(struct JSHttpClientImplementation *impl)
+{
+ if (!impl) {
+ return;
+ }
+ destroy_client_state(impl->cls);
+ impl->cls = NULL;
+ free(impl);
+}
+
diff --git a/quickjs/quickjs-http.h b/quickjs/quickjs-http.h
new file mode 100644
index 0000000..437731c
--- /dev/null
+++ b/quickjs/quickjs-http.h
@@ -0,0 +1,213 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2024 Taler Systems SA
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+
+// ## Native HTTP client library support.
+
+// Considerations:
+// - the API is designed for the HTTP client implementation
+// to run in its own thread and *not* be integrated with the
+// application's main event loop.
+// - focus on small API
+// - not a generic HTTP client, only supposed to serve the needs
+// of a JS runtime
+// - only very tiny subset of HTTP supported
+// - no request/response streaming
+// - should be appropriate to implement a JS HTTP fetch function
+// in the style of WHATWG fetch
+// - no focus on ABI compatibility whatsoever
+
+
+#ifndef _QUICKJS_HTTP_H
+#define _QUICKJS_HTTP_H
+
+#include <stdint.h>
+#include <limits.h>
+#include <stddef.h>
+
+// Forward declaration;
+struct JSHttpResponseInfo;
+
+/**
+ * Callback called when an HTTP response has arrived.
+ *
+ * IMPORTANT: May be called from an arbitrary thread.
+ */
+typedef void (*JSHttpResponseCb)(void *cls, struct JSHttpResponseInfo *resp);
+
+enum JSHttpRedirectFlag {
+ /**
+ * Handle redirects transparently.
+ */
+ JS_HTTP_REDIRECT_TRANSPARENT = 0,
+ /**
+ * Redirect status codes are returned to the client.
+ * The client can choose to follow them manually (or not).
+ */
+ JS_HTTP_REDIRECT_MANUAL = 1,
+ /**
+ * All redirect status codes result in an error.
+ */
+ JS_HTTP_REDIRECT_ERROR = 2,
+};
+
+/**
+ * Info needed to start a new HTTP request.
+ */
+struct JSHttpRequestInfo {
+ /**
+ * Callback called with the response for the request.
+ */
+ JSHttpResponseCb response_cb;
+
+ /**
+ * Closure for response_cb.
+ */
+ void *response_cb_cls;
+
+ /**
+ * Request URL.
+ */
+ const char *url;
+
+ /**
+ * Request method.
+ */
+ const char *method;
+
+ int num_request_headers;
+
+ /**
+ * Array of `num_request_headers` request headers.
+ */
+ char **request_headers;
+
+ /**
+ * 0: Handle redirects transparently.
+ * 1: Handle redirects manually.
+ * 2: Redirects result in an error.
+ */
+ enum JSHttpRedirectFlag redirect;
+
+ /**
+ * Request timeout in milliseconds.
+ *
+ * When 0 is specified, the timeout is the default request
+ * timeout for the platform.
+ *
+ * When -1 is specified, there is no timeout. This might not be
+ * supported on all platforms.
+ */
+ int timeout_ms;
+
+ /**
+ * Enable debug output for this request.
+ */
+ int debug;
+
+ /**
+ * Request body or NULL.
+ */
+ void *req_body;
+
+ /**
+ * Length or request body or 0.
+ */
+ size_t req_body_len;
+};
+
+/**
+ * Contents of an HTTP response.
+ */
+struct JSHttpResponseInfo {
+
+ /**
+ * Request that this is a response to.
+ *
+ * (Think of the request ID like a file descriptor number).
+ */
+ int request_id;
+
+ /**
+ * HTTP response status code or 0 on error.
+ */
+ int status;
+
+ /**
+ * When status is 0, error message.
+ */
+ char *errmsg;
+
+ /**
+ * Array of `num_response_headers` response headers.
+ */
+ char **response_headers;
+
+ /**
+ * Number of response headers.
+ */
+ int num_response_headers;
+
+ /**
+ * Response body or NULL.
+ */
+ void *body;
+
+ /**
+ * Length of the response body or 0.
+ */
+ size_t body_len;
+};
+
+/**
+ * Callback called when an HTTP response has arrived.
+ *
+ * IMPORTANT: May be called from an arbitrary thread.
+ */
+typedef void (*JSHttpResponseCb)(void *cls, struct JSHttpResponseInfo *resp);
+
+/**
+ * Function to create a new HTTP fetch request.
+ * The request can still be configured until it is started.
+ * An identifier for the request will be written to @a handle.
+ *
+ * @return negative number on error, positive request_id on success
+ */
+typedef int (*JSHttpReqCreateFn)(void *cls, struct JSHttpRequestInfo *req_info);
+
+/**
+ * Cancel a request. The request_id will become invalid
+ * and the callback won't be called with request_id.
+ */
+typedef int (*JSHttpReqCancelFn)(void *cls, int request_id);
+
+struct JSHttpClientImplementation {
+ /**
+ * Opaque closure passed to client functions.
+ */
+ void *cls;
+ JSHttpReqCreateFn req_create;
+ JSHttpReqCancelFn req_cancel;
+};
+
+
+struct JSHttpClientImplementation *
+js_curl_http_client_create(void);
+
+void
+js_curl_http_client_destroy(struct JSHttpClientImplementation *impl);
+
+#endif /* _QUICKJS_HTTP_H */
diff --git a/quickjs/quickjs-libc.c b/quickjs/quickjs-libc.c
index 75114a6..acecd8b 100644
--- a/quickjs/quickjs-libc.c
+++ b/quickjs/quickjs-libc.c
@@ -79,10 +79,11 @@ typedef sig_t sighandler_t;
*/
#ifndef NO_HTTP
-#include <curl/curl.h>
#include <arpa/inet.h>
+#include "quickjs-http.h"
#endif
+
typedef struct {
struct list_head link;
int fd;
@@ -135,6 +136,23 @@ typedef struct {
typedef struct {
struct list_head link;
+ int request_id;
+ int status;
+ char *errmsg;
+ char **response_headers;
+ void *body;
+ size_t body_len;
+} JSHttpMessage;
+
+typedef struct {
+ pthread_mutex_t mutex;
+ struct list_head msg_queue; /* list of JSHttpMessage.link */
+ int read_fd;
+ int write_fd;
+} JSHttpMessagePipe;
+
+typedef struct {
+ struct list_head link;
JSWorkerMessagePipe *recv_pipe;
JSValue on_message_func;
} JSWorkerMessageHandler;
@@ -151,6 +169,9 @@ typedef struct JSThreadState {
// send/receive message to/from the host in the main thread
JSHostMessagePipe *host_pipe;
+ // receive messages from the HTTP client thread
+ JSHttpMessagePipe *http_pipe;
+
JSValue on_host_message_func;
JSHostMessageHandlerFn host_message_handler_f;
@@ -158,11 +179,12 @@ typedef struct JSThreadState {
int is_worker_thread;
+ struct list_head http_requests;
+
#ifndef NO_HTTP
- CURLM *curlm;
- CURLSH *curlsh;
- struct list_head curl_requests;
+ struct JSHttpClientImplementation *http_client_impl;
#endif
+
} JSThreadState;
static uint64_t os_pending_signals;
@@ -2106,233 +2128,246 @@ static void js_os_timer_mark(JSRuntime *rt, JSValueConst val,
typedef struct {
// linked list of all requests
struct list_head link;
- DynBuf response_data;
- // curl request headers, must be kept around during the request
- struct curl_slist *headers;
- // Response headers
- JSValue headers_list;
- JSValue resolve_func;
- JSValue reject_func;
- JSContext* ctx;
- uint8_t *readbuf;
- size_t readpos;
- size_t readlen;
- CURL *curl;
- BOOL client_has_accept_header;
- BOOL client_has_content_type_header;
-} 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;
-}
+ int request_id;
-static size_t curl_write_cb(void *data, size_t size, size_t nmemb, void *userp)
-{
- size_t realsize = size * nmemb;
- CurlRequestContext *rctx = userp;
+ JSValue resolve_func;
+ JSValue reject_func;
- if (0 != dbuf_put(&rctx->response_data, data, realsize)) {
- return 0;
- }
+ JSContext* ctx;
+} HttpRequestContext;
- return realsize;
-}
-size_t read_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
+void js_os_set_http_impl(JSRuntime *rt, struct JSHttpClientImplementation *impl)
{
- CurlRequestContext *rctx = userdata;
- ssize_t src_available = rctx->readlen - rctx->readpos;
- size_t dst_available = size * nmemb;
- size_t n;
+ JSThreadState *ts = JS_GetRuntimeOpaque(rt);
- 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;
+ ts->http_client_impl = impl;
}
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;
-}
+ JSValue prop_val;
+ BOOL bool_val;
-BOOL starts_with_ignorecase(const char *str, const char *prefix)
-{
- for (int i = 0; i < strlen(prefix); i++) {
- if (!str[i]) {
- return FALSE;
- }
- if (tolower(str[i]) != tolower(prefix[i])) {
- return FALSE;
+ prop_val = JS_GetPropertyStr(ctx, this_val, prop_name);
+ if (JS_IsException(prop_val)) {
+ return -1;
}
- }
- return TRUE;
+ bool_val = JS_ToBool(ctx, prop_val);
+ JS_FreeValue(ctx, prop_val);
+ return bool_val;
}
-static int gather_headers(JSContext *ctx, JSValueConst js_headers, CurlRequestContext *req_context)
-{
- JSValue length_prop;
- uint32_t length;
-
- length_prop = JS_GetPropertyStr(ctx, js_headers, "length");
- if (JS_IsException(length_prop)) {
- return -1;
- }
- if (0 != JS_ToUint32(ctx, &length, length_prop)) {
- return -1;
- }
- JS_FreeValue(ctx, length_prop);
-
- for (uint32_t i = 0; i < length; i++) {
- JSValue item = JS_GetPropertyUint32(ctx, js_headers, i);
- if (JS_IsException(item)) {
- goto exception;
- }
- const char *cstr = JS_ToCString(ctx, item);
- if (starts_with_ignorecase(cstr, "accept:")) {
- req_context->client_has_accept_header = TRUE;
- } else if (starts_with_ignorecase(cstr, "content-type:")) {
- req_context->client_has_content_type_header = TRUE;
- }
- if (!cstr) {
- JS_FreeValue(ctx, item);
- goto exception;
- }
- req_context->headers = curl_slist_append (req_context->headers, cstr);
- JS_FreeCString(ctx, cstr);
- JS_FreeValue(ctx, item);
- }
- return 0;
-exception:
- return -1;
-}
-
-static void free_fetch_request_context(CurlRequestContext *req_context)
+static void free_http_request_context(HttpRequestContext *req_context)
{
JSContext *ctx;
+ JSThreadState *ts;
if (!req_context) {
return;
}
ctx = req_context->ctx;
+ ts = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
+ ts->http_client_impl->req_cancel(ts->http_client_impl->cls, req_context->request_id);
req_context->ctx = NULL;
- if (req_context->curl) {
- curl_easy_cleanup(req_context->curl);
- req_context->curl = NULL;
- }
- if (NULL != req_context->readbuf) {
- free(req_context->readbuf);
- }
- dbuf_free(&req_context->response_data);
- JS_FreeValue(ctx, req_context->headers_list);
JS_FreeValue(ctx, req_context->resolve_func);
JS_FreeValue(ctx, req_context->reject_func);
- curl_slist_free_all(req_context->headers);
if (NULL != req_context->link.prev) {
- list_del(&req_context->link);
+ list_del(&req_context->link);
}
js_free(ctx, req_context);
}
-static void
-finish_fetch_http(CurlRequestContext *req_context, CURLcode result)
+static void js_free_http_message(JSHttpMessage *msg)
{
+ if (msg->body) {
+ free(msg->body);
+ msg->body = NULL;
+ }
+ if (msg->errmsg) {
+ free(msg->errmsg);
+ msg->errmsg = NULL;
+ }
+ if (msg->response_headers) {
+ char **h;
+ for (h = msg->response_headers; *h; h++) {
+ free(*h);
+ }
+ free(msg->response_headers);
+ msg->response_headers = NULL;
+ }
+ free(msg);
+}
+
+static void handle_http_resp(void *cls, struct JSHttpResponseInfo *resp_info)
+{
+ // Called from a different thread.
+ // We must enqueue something that the message loop will process
+ //
+ HttpRequestContext *req_context = cls;
JSContext *ctx = req_context->ctx;
- long resp_code;
- JSValue ret_val;
- JSValue cb_ret;
-
- if (CURLE_OK != result) {
- const char *errmsg;
- JSAtom atom_message;
-
- atom_message = JS_NewAtom(ctx, "message");
- errmsg = curl_easy_strerror(result);
- ret_val = JS_NewError(ctx);
- JS_DefinePropertyValue(ctx, ret_val, atom_message,
- JS_NewString(ctx, errmsg),
- JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
- cb_ret = JS_Call(ctx, req_context->reject_func, JS_UNDEFINED, 1, &ret_val);
- JS_FreeAtom(ctx, atom_message);
- JS_FreeValue(ctx, cb_ret);
- JS_FreeValue(ctx, ret_val);
- free_fetch_request_context(req_context);
- return;
+ JSThreadState *ts = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
+ JSHttpMessage *msg;
+ JSHttpMessagePipe *hp;
+
+ msg = malloc(sizeof (*msg));
+ if (!msg) {
+ goto fail;
+ }
+ memset(msg, 0, sizeof (*msg));
+
+ msg->status = resp_info->status;
+ msg->request_id = resp_info->request_id;
+
+ if (resp_info->response_headers) {
+ int num_headers = 0;
+ char **h;
+
+ h = resp_info->response_headers;
+ while (*h) {
+ num_headers++;
+ h++;
+ }
+
+ msg->response_headers = malloc((num_headers + 1) * sizeof (char *));
+ if (!msg->response_headers) {
+ goto fail;
+ }
+ memset(msg->response_headers, 0, (num_headers + 1) * sizeof (char *));
+ for (int i = 0; i < num_headers; i++) {
+ msg->response_headers[i] = strdup(resp_info->response_headers[i]);
+ if (!msg->response_headers[i]) {
+ goto fail;
+ }
+ }
+ } else {
+ msg->response_headers = NULL;
}
- ret_val = JS_NewObject(ctx);
- if (JS_IsException(ret_val)) {
- // Huh, we're out of memory in the request handler?
- fprintf(stderr, "fatal: can't allocate object in finish_fetch_http\n");
+ if (resp_info->errmsg != NULL) {
+ msg->errmsg = strdup(resp_info->errmsg);
+ if (!msg->errmsg) {
+ goto fail;
+ }
+ }
+
+ if (resp_info->body_len > 0) {
+ msg->body = malloc(resp_info->body_len);
+ if (!msg->body) {
+ goto fail;
+ }
+ msg->body_len = resp_info->body_len;
+ memcpy(msg->body, resp_info->body, resp_info->body_len);
+ }
+
+ hp = ts->http_pipe;
+ pthread_mutex_lock(&hp->mutex);
+ /* indicate that data is present */
+ if (list_empty(&hp->msg_queue)) {
+ uint8_t ch = '\0';
+ int ret;
+ for(;;) {
+ ret = write(hp->write_fd, &ch, 1);
+ if (ret == 1)
+ break;
+ if (ret < 0 && (errno != EAGAIN || errno != EINTR))
+ break;
+ }
+ }
+ list_add_tail(&msg->link, &hp->msg_queue);
+ pthread_mutex_unlock(&hp->mutex);
+ return;
+ fail:
+ js_free_http_message(msg);
+ return;
+}
+
+static void
+free_http_headers(JSContext *ctx, char **headers)
+{
+ if (!headers) {
return;
}
+ for (char **h = headers; *h != NULL; h++) {
+ js_free(ctx, *h);
+ }
+ js_free(ctx, headers);
+}
+
+static char **gather_http_headers(JSContext *ctx, JSValueConst js_headers)
+{
+ JSValue length_prop;
+ uint32_t length;
+ char **headers = NULL;
+
+ length_prop = JS_GetPropertyStr(ctx, js_headers, "length");
+ if (JS_IsException(length_prop)) {
+ return NULL;
+ }
+ if (0 != JS_ToUint32(ctx, &length, length_prop)) {
+ return NULL;
+ }
+ JS_FreeValue(ctx, length_prop);
- curl_easy_getinfo(req_context->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));
- cb_ret = JS_Call(ctx, req_context->resolve_func, JS_UNDEFINED, 1, &ret_val);
- JS_FreeValue(ctx, cb_ret);
- JS_FreeValue(ctx, ret_val);
- free_fetch_request_context(req_context);
+ headers = js_mallocz(ctx, (length + 1) * sizeof (char *));
+ if (!headers) {
+ goto exception;
+ }
+
+ for (uint32_t i = 0; i < length; i++) {
+ char *hval;
+ JSValue item = JS_GetPropertyUint32(ctx, js_headers, i);
+ if (JS_IsException(item)) {
+ goto exception;
+ }
+ const char *cstr = JS_ToCString(ctx, item);
+ if (!cstr) {
+ JS_FreeValue(ctx, item);
+ goto exception;
+ }
+ hval = js_strdup(ctx, cstr);
+ if (!hval) {
+ goto exception;
+ }
+ JS_FreeCString(ctx, cstr);
+ JS_FreeValue(ctx, item);
+ headers[i] = hval;
+ }
+ return headers;
+exception:
+ free_http_headers(ctx, headers);
+ return NULL;
}
+
/**
* fetchHttp(url, { method, headers, body }): Promise<Response>
*/
static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val,
- int argc, JSValueConst *argv)
+ int argc, JSValueConst *argv)
{
+ JSValue ret_val = JS_UNINITIALIZED;
JSRuntime *rt = JS_GetRuntime(ctx);
JSThreadState *ts = JS_GetRuntimeOpaque(rt);
- const char *req_url = NULL;
- JSValue ret_val = JS_UNDEFINED;
JSValue resolving_funs[2];
JSValue options = JS_UNINITIALIZED;
JSValue method = JS_UNINITIALIZED;
const char *method_str = NULL;
- CURLMcode mres;
- CurlRequestContext *req_context = {0};
+ const char *req_url = NULL;
+ struct JSHttpRequestInfo req = { 0 };
+ HttpRequestContext *req_context = NULL;
BOOL debug = FALSE;
- BOOL req_started = FALSE;
+ int redirect = 0;
+ int ret;
- req_context = js_mallocz(ctx, sizeof *req_context);
+ if (NULL == ts->http_client_impl) {
+ JS_ThrowInternalError(ctx, "no HTTP client implementation available");
+ goto exception;
+ }
+ req_context = js_mallocz(ctx, sizeof *req_context);
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) {
@@ -2342,116 +2377,106 @@ static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val,
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) {
+ } else if (JS_VALUE_GET_TAG(options) == JS_TAG_OBJECT) {
+ int has_prop_redirect;
+
method = JS_GetPropertyStr(ctx, options, "method");
debug = expect_property_str_bool(ctx, options, "debug");
+
+ has_prop_redirect = JS_HasPropertyStr(ctx, options, "redirect");
+ if (has_prop_redirect < 0) {
+ goto exception;
+ }
+ if (has_prop_redirect) {
+ int32_t redir_num;
+ JSValue redir_val = JS_GetPropertyStr(ctx, options, "redirect");
+ if (JS_IsException(redir_val)) {
+ goto exception;
+ }
+ if (JS_ToInt32(ctx, &redir_num, redir_val)) {
+ goto exception;
+ }
+ if (redir_num < 0 || redir_num > JS_HTTP_REDIRECT_ERROR) {
+ JS_ThrowTypeError(ctx, "redirect option out of range");
+ goto exception;
+ }
+ redirect = redir_num;
+ }
} else {
JS_ThrowTypeError(ctx, "invalid options");
goto exception;
}
- req_context->curl = curl_easy_init();
- if (!req_context->curl) {
- JS_ThrowInternalError(ctx, "unable to init libcurl");
- goto exception;
- }
- curl_easy_setopt(req_context->curl, CURLOPT_PRIVATE, req_context);
- curl_easy_setopt(req_context->curl, CURLOPT_SHARE, ts->curlsh);
- curl_easy_setopt(req_context->curl, CURLOPT_URL, req_url);
-// curl_easy_setopt(req_context->curl, CURLOPT_DNS_SERVERS, "8.8.8.8");
- curl_easy_setopt(req_context->curl, CURLOPT_DNS_SERVERS, "9.9.9.9");
- curl_easy_setopt(req_context->curl, CURLOPT_USERAGENT, "qtart");
- curl_easy_setopt(req_context->curl, CURLOPT_CAINFO, "/etc/ssl/certs/ca-certificates.crt");
- curl_easy_setopt(req_context->curl, CURLOPT_HEADERFUNCTION, curl_header_callback);
- curl_easy_setopt(req_context->curl, CURLOPT_HEADERDATA, req_context);
- curl_easy_setopt(req_context->curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
- curl_easy_setopt(req_context->curl, CURLOPT_WRITEDATA, req_context);
- if (debug == TRUE) {
- curl_easy_setopt(req_context->curl, CURLOPT_VERBOSE, 1L);
- }
-
- // FIXME: This is only a temporary hack until we have proper TLS CA support
- // on all platforms
- curl_easy_setopt(req_context->curl, CURLOPT_SSL_VERIFYPEER, 0);
- curl_easy_setopt(req_context->curl, CURLOPT_SSL_VERIFYHOST, 0);
-
if (JS_VALUE_GET_TAG(options) == JS_TAG_OBJECT) {
JSValue header_item = JS_GetPropertyStr(ctx, options, "headers");
if (JS_IsException(header_item)) {
goto exception;
}
if (JS_VALUE_GET_TAG(header_item) == JS_TAG_OBJECT) {
- if (0 != gather_headers(ctx, header_item, req_context)) {
- JS_FreeValue(ctx, header_item);
- goto exception;
+ char **headers = gather_http_headers(ctx, header_item);
+ if (NULL == headers) {
+ JS_FreeValue(ctx, header_item);
+ goto exception;
}
+ req.request_headers = headers;
}
JS_FreeValue(ctx, header_item);
}
- method_str = JS_ToCString(ctx, method);
-
- if (0 == strcasecmp(method_str, "get")) {
- curl_easy_setopt(req_context->curl, CURLOPT_HTTPGET, 1L);
- if (!req_context->client_has_accept_header) {
- req_context->headers = curl_slist_append (req_context->headers, "Accept: application/json");
- }
- } else if (0 == strcasecmp(method_str, "delete")) {
- curl_easy_setopt(req_context->curl, CURLOPT_HTTPGET, 1L);
- curl_easy_setopt(req_context->curl, CURLOPT_CUSTOMREQUEST, "DELETE");
- } else if ((0 == strcasecmp(method_str, "post")) ||
- (0 == strcasecmp(method_str, "put"))) {
+ if (JS_VALUE_GET_TAG(options) == JS_TAG_OBJECT) {
JSValue data;
uint8_t *data_ptr;
size_t data_len;
+ int has_prop;
- data = JS_GetPropertyStr(ctx, options, "data");
- if (JS_IsException(data)) {
- goto exception;
- }
- data_ptr = JS_GetArrayBuffer(ctx, &data_len, data);
- if (!data_ptr) {
+ has_prop = JS_HasPropertyStr(ctx, options, "data");
+
+ if (-1 == has_prop) {
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(req_context->curl, CURLOPT_POST, 1L);
- if (0 == strcasecmp(method_str, "put")) {
- curl_easy_setopt(req_context->curl, CURLOPT_CUSTOMREQUEST, "PUT");
- }
- curl_easy_setopt(req_context->curl, CURLOPT_READFUNCTION, read_callback);
- curl_easy_setopt(req_context->curl, CURLOPT_READDATA, req_context);
- if (!req_context->client_has_content_type_header) {
- req_context->headers = curl_slist_append(req_context->headers, "Content-Type: application/json");
+
+ if (has_prop) {
+ data = JS_GetPropertyStr(ctx, options, "data");
+ if (JS_IsException(data)) {
+ goto exception;
+ }
+ if (!JS_IsNull(data) && !JS_IsUndefined(data)) {
+ data_ptr = JS_GetArrayBuffer(ctx, &data_len, data);
+ if (!data_ptr) {
+ goto exception;
+ }
+ }
+ req.req_body = data_ptr;
+ req.req_body_len = data_len;
}
- } else {
- JS_ThrowTypeError(ctx, "invalid request method");
- goto exception;
}
- curl_easy_setopt(req_context->curl, CURLOPT_HTTPHEADER, req_context->headers);
+ method_str = JS_ToCString(ctx, method);
+
+ req.method = method_str;
+ req.url = req_url;
+ req.debug = debug;
+ req.redirect = redirect;
+ req.response_cb = &handle_http_resp;
+ req.response_cb_cls = req_context;
+ ret = ts->http_client_impl->req_create(ts->http_client_impl->cls, &req);
- mres = curl_multi_add_handle(ts->curlm, req_context->curl);
- if (CURLM_OK != mres) {
- JS_ThrowInternalError(ctx, "fetch failed: %s", curl_multi_strerror(mres));
+ if (ret < 0) {
+ JS_ThrowInternalError(ctx, "failed to create request");
goto exception;
}
- list_add_tail(&req_context->link, &ts->curl_requests);
- req_started = TRUE;
+
+ list_add_tail(&req_context->link, &ts->http_requests);
ret_val = JS_NewPromiseCapability(ctx, resolving_funs);
if (JS_IsException(ret_val)) {
goto done;
}
+ req_context->request_id = ret;
req_context->resolve_func = resolving_funs[0];
req_context->reject_func = resolving_funs[1];
done:
- if (FALSE == req_started) {
- free_fetch_request_context(req_context);
- }
+ free_http_headers(ctx, req.request_headers);
JS_FreeValue(ctx, method);
JS_FreeCString(ctx, req_url);
JS_FreeCString(ctx, method_str);
@@ -2459,6 +2484,7 @@ done:
exception:
ret_val = JS_EXCEPTION;
goto done;
+
}
#endif
@@ -2735,38 +2761,97 @@ static int handle_host_message(JSRuntime *rt, JSContext *ctx)
return ret;
}
-// Perform curl network requests and
-static void do_curl(JSContext *ctx)
+/* return 1 if a message was handled, 0 if no message */
+static int handle_http_message(JSRuntime *rt, JSContext *ctx)
{
#ifndef NO_HTTP
- JSRuntime *rt = JS_GetRuntime(ctx);
- JSThreadState *ts = JS_GetRuntimeOpaque(rt);
- CURLMcode curl_ret;
- struct CURLMsg *m;
- int running_handles;
+ JSThreadState *ts = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
+ JSHttpMessagePipe *hp = ts->http_pipe;
+ int ret;
+ struct list_head *el;
+ struct list_head *req_el;
+ JSHttpMessage *msg;
+ JSValue obj, func, retval;
+ HttpRequestContext *request_ctx;
+
+ pthread_mutex_lock(&hp->mutex);
+ if (!list_empty(&hp->msg_queue)) {
+ el = hp->msg_queue.next;
+ msg = list_entry(el, JSHttpMessage, link);
- curl_ret = curl_multi_perform(ts->curlm, &running_handles);
- if (0 != curl_ret) {
- fprintf(stderr, "curlm error: %s\n", curl_multi_strerror(curl_ret));
- return;
- }
+ /* remove the message from the queue */
+ list_del(&msg->link);
- do {
- int msgq = 0;
- m = curl_multi_info_read(ts->curlm, &msgq);
- if (m && (m->msg == CURLMSG_DONE)) {
- CurlRequestContext *req_ctx;
- CURL *e = m->easy_handle;
- CURLcode result = m->data.result;
- if (CURLE_OK != curl_easy_getinfo(e, CURLINFO_PRIVATE, &req_ctx)) {
- fprintf(stderr, "fatal: curl handle has no private data");
- continue;
+ if (list_empty(&hp->msg_queue)) {
+ uint8_t buf[16];
+ int ret;
+ for(;;) {
+ ret = read(hp->read_fd, buf, sizeof(buf));
+ if (ret >= 0)
+ break;
+ if (errno != EAGAIN && errno != EINTR)
+ break;
}
- curl_multi_remove_handle(ts->curlm, e);
- finish_fetch_http(req_ctx, result);
}
- } while (m);
-#endif
+
+ pthread_mutex_unlock(&hp->mutex);
+
+ list_for_each(req_el, &ts->http_requests) {
+ request_ctx = list_entry(req_el, HttpRequestContext, link);
+ if (request_ctx->request_id == msg->request_id) {
+ if (msg->status != 0) {
+ JSValue headers_list = JS_NewArray(ctx);
+
+ obj = JS_NewObject(ctx);
+
+ if (msg->response_headers) {
+ char **h = msg->response_headers;
+ while (*h) {
+ qjs_array_append_new(ctx, headers_list, JS_NewString(ctx, *h));
+ h++;
+ }
+ }
+ JS_SetPropertyStr(ctx, obj, "headers", headers_list);
+
+ //JS_SetPropertyStr(ctx, obj, "data", JS_NewTypedArray(ctx, JS_NewArrayBufferCopy(ctx, msg->body, msg->body_len), 1));
+ JS_SetPropertyStr(ctx, obj, "data", JS_NewArrayBufferCopy(ctx, msg->body, msg->body_len));
+
+ JS_SetPropertyStr(ctx, obj, "status", JS_NewInt32(ctx, msg->status));
+ func = JS_DupValue(ctx, request_ctx->resolve_func);
+ retval = JS_Call(ctx, func, JS_UNDEFINED, 1, (JSValueConst *)&obj);
+ JS_FreeValue(ctx, obj);
+ JS_FreeValue(ctx, func);
+ if (JS_IsException(retval)) {
+ js_std_dump_error(ctx);
+ } else {
+ JS_FreeValue(ctx, retval);
+ }
+ } else {
+ JSAtom atom_message;
+
+ atom_message = JS_NewAtom(ctx, "message");
+ obj = JS_NewError(ctx);
+ JS_DefinePropertyValue(ctx, obj, atom_message,
+ JS_NewString(ctx, msg->errmsg),
+ JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+ retval = JS_Call(ctx, request_ctx->reject_func, JS_UNDEFINED, 1, &obj);
+ JS_FreeAtom(ctx, atom_message);
+ JS_FreeValue(ctx, retval);
+ }
+ break;
+ }
+ }
+
+ js_free_http_message(msg);
+ ret = 1;
+ } else {
+ pthread_mutex_unlock(&hp->mutex);
+ ret = 0;
+ }
+ return ret;
+#else
+ return 0;
+#endif /* NO_HTTP */
}
static int js_os_poll(JSContext *ctx)
@@ -2799,7 +2884,7 @@ static int js_os_poll(JSContext *ctx)
}
#ifndef NO_HTTP
- have_http_requests = !list_empty(&ts->curl_requests);
+ have_http_requests = !list_empty(&ts->http_requests);
#endif
if ((!have_http_requests) && list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_timers) &&
@@ -2860,36 +2945,8 @@ static int js_os_poll(JSContext *ctx)
fd_max = max_int(fd_max, ts->host_pipe->read_fd);
FD_SET(ts->host_pipe->read_fd, &rfds);
-#ifndef NO_HTTP
- {
- int curl_fd_max = -1;
- CURLMcode mret;
- long timeo = -1;
-
- mret = curl_multi_fdset(ts->curlm, &rfds, &wfds, &ecxfds, &curl_fd_max);
- if (CURLM_OK != mret) {
- fprintf(stderr, "curlm error: %s\n", curl_multi_strerror(mret));
- } else if (curl_fd_max != -1) {
- fd_max = max_int(fd_max, curl_fd_max);
- }
-
- curl_multi_timeout(ts->curlm, &timeo);
- if (timeo > 0) {
- long timeo_sec = timeo / 1000;
- long timeo_usec = (timeo % 1000) * 1000;
- tvp = &tv;
- if (tv.tv_sec < timeo_sec) {
- tv.tv_sec = timeo_sec;
- tv.tv_usec = timeo_usec;
- } else if ((tv.tv_sec == timeo_sec) && tv.tv_usec < timeo_usec) {
- tv.tv_usec = timeo_usec;
- }
- } else if (timeo == 0) {
- do_curl(ctx);
- goto done;
- }
- }
-#endif
+ fd_max = max_int(fd_max, ts->http_pipe->read_fd);
+ FD_SET(ts->http_pipe->read_fd, &rfds);
ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp);
if (ret > 0) {
@@ -2926,7 +2983,11 @@ static int js_os_poll(JSContext *ctx)
}
}
- do_curl(ctx);
+ if (FD_ISSET(ts->http_pipe->read_fd, &rfds)) {
+ if (handle_http_message(rt, ctx)) {
+ goto done;
+ }
+ }
}
done:
return 0;
@@ -3811,6 +3872,27 @@ static JSHostMessagePipe *js_new_host_message_pipe(void)
return ps;
}
+static JSHttpMessagePipe *js_new_http_message_pipe(void)
+{
+ JSHttpMessagePipe *ps;
+ int pipe_fds[2];
+
+ if (pipe(pipe_fds) < 0)
+ return NULL;
+
+ ps = malloc(sizeof(*ps));
+ if (!ps) {
+ close(pipe_fds[0]);
+ close(pipe_fds[1]);
+ return NULL;
+ }
+ init_list_head(&ps->msg_queue);
+ pthread_mutex_init(&ps->mutex, NULL);
+ ps->read_fd = pipe_fds[0];
+ ps->write_fd = pipe_fds[1];
+ return ps;
+}
+
static JSWorkerMessagePipe *js_dup_message_pipe(JSWorkerMessagePipe *ps)
{
atomic_add_int(&ps->ref_count, 1);
@@ -3880,6 +3962,32 @@ static void js_free_host_message_pipe(JSHostMessagePipe *ps)
}
}
+#ifndef NO_HTTP
+
+static void js_free_http_message_pipe(JSHttpMessagePipe *ps)
+{
+ struct list_head *el, *el1;
+ JSHttpMessage *msg;
+ int ref_count;
+
+ if (!ps)
+ return;
+
+ assert(ref_count >= 0);
+ if (ref_count == 0) {
+ list_for_each_safe(el, el1, &ps->msg_queue) {
+ msg = list_entry(el, JSHttpMessage, link);
+ js_free_http_message(msg);
+ }
+ pthread_mutex_destroy(&ps->mutex);
+ close(ps->read_fd);
+ close(ps->write_fd);
+ free(ps);
+ }
+}
+
+#endif
+
static void js_free_port(JSRuntime *rt, JSWorkerMessageHandler *port)
{
if (port) {
@@ -4416,13 +4524,6 @@ 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);
-#ifndef NO_HTTP
- if (CURLE_OK != curl_global_init (CURL_GLOBAL_DEFAULT)) {
- JS_ThrowInternalError(ctx, "unable to init libcurl (global)");
- return -1;
- }
-#endif
-
#ifdef USE_WORKER
{
JSRuntime *rt = JS_GetRuntime(ctx);
@@ -4537,6 +4638,7 @@ oom_fail:
init_list_head(&ts->port_list);
ts->on_host_message_func = JS_NULL;
ts->host_pipe = js_new_host_message_pipe();
+ ts->http_pipe = js_new_http_message_pipe();
if (!ts->host_pipe) {
goto oom_fail;
}
@@ -4544,12 +4646,7 @@ oom_fail:
JS_SetRuntimeOpaque(rt, ts);
#ifndef NO_HTTP
- ts->curlm = curl_multi_init();
- ts->curlsh = curl_share_init();
- curl_share_setopt(ts->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
- curl_share_setopt(ts->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
- curl_share_setopt(ts->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
- init_list_head(&ts->curl_requests);
+ init_list_head(&ts->http_requests);
#endif
#ifdef USE_WORKER
@@ -4588,16 +4685,10 @@ void js_std_free_handlers(JSRuntime *rt)
}
#ifndef NO_HTTP
-
- list_for_each_safe(el, el1, &ts->curl_requests) {
- CurlRequestContext *request_ctx = list_entry(el, CurlRequestContext, link);
- free_fetch_request_context(request_ctx);
+ list_for_each_safe(el, el1, &ts->http_requests) {
+ HttpRequestContext *request_ctx = list_entry(el, HttpRequestContext, link);
+ free_http_request_context(request_ctx);
}
-
- curl_multi_cleanup(ts->curlm);
- ts->curlm = NULL;
- curl_share_cleanup(ts->curlsh);
- ts->curlsh = NULL;
#endif
JS_FreeValueRT(rt, ts->on_host_message_func);
@@ -4610,6 +4701,10 @@ void js_std_free_handlers(JSRuntime *rt)
js_free_host_message_pipe(ts->host_pipe);
+#ifndef NO_HTTP
+ js_free_http_message_pipe(ts->http_pipe);
+#endif
+
free(ts);
JS_SetRuntimeOpaque(rt, NULL); /* fail safe */
}
diff --git a/quickjs/quickjs-libc.h b/quickjs/quickjs-libc.h
index 4a7697e..650ff36 100644
--- a/quickjs/quickjs-libc.h
+++ b/quickjs/quickjs-libc.h
@@ -29,6 +29,10 @@
#include "quickjs.h"
+#ifndef NO_HTTP
+#include "quickjs-http.h"
+#endif
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -55,6 +59,10 @@ void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise,
void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt));
void js_os_set_host_message_handler(JSContext *ctx, JSHostMessageHandlerFn f, void *cls);
int js_os_post_message_from_host(JSContext *ctx, const char *msg_str);
+
+#ifndef NO_HTTP
+void js_os_set_http_impl(JSRuntime *rt, struct JSHttpClientImplementation *impl);
+#endif
#ifdef __cplusplus
} /* extern "C" { */
diff --git a/quickjs/quickjs.c b/quickjs/quickjs.c
index bae5541..561374b 100644
--- a/quickjs/quickjs.c
+++ b/quickjs/quickjs.c
@@ -7776,6 +7776,17 @@ int JS_PreventExtensions(JSContext *ctx, JSValueConst obj)
}
/* return -1 if exception otherwise TRUE or FALSE */
+int JS_HasPropertyStr(JSContext *ctx, JSValueConst obj, const char *propname)
+{
+ JSAtom atom;
+ int ret;
+ atom = JS_NewAtom(ctx, propname);
+ ret = JS_HasProperty(ctx, obj, atom);
+ JS_FreeAtom(ctx, atom);
+ return ret;
+}
+
+/* return -1 if exception otherwise TRUE or FALSE */
int JS_HasProperty(JSContext *ctx, JSValueConst obj, JSAtom prop)
{
JSObject *p;
diff --git a/quickjs/quickjs.h b/quickjs/quickjs.h
index c1cf768..d616c16 100644
--- a/quickjs/quickjs.h
+++ b/quickjs/quickjs.h
@@ -751,6 +751,7 @@ int JS_SetPropertyInt64(JSContext *ctx, JSValueConst this_obj,
int JS_SetPropertyStr(JSContext *ctx, JSValueConst this_obj,
const char *prop, JSValue val);
int JS_HasProperty(JSContext *ctx, JSValueConst this_obj, JSAtom prop);
+int JS_HasPropertyStr(JSContext *ctx, JSValueConst this_obj, const char *propname);
int JS_IsExtensible(JSContext *ctx, JSValueConst obj);
int JS_PreventExtensions(JSContext *ctx, JSValueConst obj);
int JS_DeleteProperty(JSContext *ctx, JSValueConst obj, JSAtom prop, int flags);
diff --git a/taler_wallet_core_lib.c b/taler_wallet_core_lib.c
index ae82358..a7c2898 100644
--- a/taler_wallet_core_lib.c
+++ b/taler_wallet_core_lib.c
@@ -52,6 +52,8 @@ struct TALER_WALLET_Instance
TALER_WALLET_LogHandlerFn log_handler_f;
void *log_handler_cls;
+
+ struct JSHttpClientImplementation *http_impl;
};
@@ -176,8 +178,14 @@ run(void *cls)
wh->rt = JS_NewRuntime();
js_std_init_handlers(wh->rt);
- wh->ctx = JS_NewCustomContext(wh->rt);
+ if (wh->http_impl) {
+ js_os_set_http_impl(wh->rt, wh->http_impl);
+ } else {
+ fprintf(stderr, "warning: no HTTP client implementation provided for wallet-core\n");
+ }
+
+ wh->ctx = JS_NewCustomContext(wh->rt);
if (!wh->ctx) {
fprintf(stderr, "qjs: cannot allocate JS context\n");
@@ -322,3 +330,10 @@ TALER_start_redirect_std(TALER_LogFn logfn, void *cls)
pthread_detach(log_thr);
return 0;
}
+
+void
+TALER_set_http_client_implementation(struct TALER_WALLET_Instance *instance,
+ struct JSHttpClientImplementation *impl)
+{
+ instance->http_impl = impl;
+}
diff --git a/taler_wallet_core_lib.h b/taler_wallet_core_lib.h
index 166612d..660690d 100644
--- a/taler_wallet_core_lib.h
+++ b/taler_wallet_core_lib.h
@@ -25,6 +25,8 @@
#ifndef _TALER_WALLET_LIB_H
#define _TALER_WALLET_LIB_H
+#include "quickjs/quickjs-http.h"
+
/**
* Opaque handle to a Taler wallet-core instance.
*/
@@ -153,4 +155,15 @@ typedef void (*TALER_LogFn)(void *cls, int stream, const char *msg);
int
TALER_start_redirect_std(TALER_LogFn logfn, void *cls);
+
+/**
+ * Set the HTTP client implementation to be used by the wallet.
+ *
+ * @param instance wallet-core instance
+ * @param HTTP client implementation
+ */
+void
+TALER_set_http_client_implementation(struct TALER_WALLET_Instance *instance,
+ struct JSHttpClientImplementation *impl);
+
#endif /*_TALER_WALLET_LIB_H */
diff --git a/taler_wallet_core_lib_preprocessed.h b/taler_wallet_core_lib_preprocessed.h
new file mode 100644
index 0000000..a050eb1
--- /dev/null
+++ b/taler_wallet_core_lib_preprocessed.h
@@ -0,0 +1,381 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2014-2022 Taler Systems SA
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * C interface to the functionality of wallet-core.
+ *
+ * Currently, the underlying implementation uses the JS implementation of
+ * wallet-core, but this may (or may not) change in the future.
+ *
+ * @author Florian Dold
+ */
+#ifndef _TALER_WALLET_LIB_H
+#define _TALER_WALLET_LIB_H
+
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2024 Taler Systems SA
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+
+// ## Native HTTP client library support.
+
+// Considerations:
+// - the API is designed for the HTTP client implementation
+// to run in its own thread and *not* be integrated with the
+// application's main event loop.
+// - focus on small API
+// - not a generic HTTP client, only supposed to serve the needs
+// of a JS runtime
+// - only very tiny subset of HTTP supported
+// - no request/response streaming
+// - should be appropriate to implement a JS HTTP fetch function
+// in the style of WHATWG fetch
+// - no focus on ABI compatibility whatsoever
+
+
+#ifndef _QUICKJS_HTTP_H
+#define _QUICKJS_HTTP_H
+
+#include <stdint.h>
+#include <limits.h>
+#include <stddef.h>
+
+// Forward declaration;
+struct JSHttpResponseInfo;
+
+/**
+ * Callback called when an HTTP response has arrived.
+ *
+ * IMPORTANT: May be called from an arbitrary thread.
+ */
+typedef void (*JSHttpResponseCb)(void *cls, struct JSHttpResponseInfo *resp);
+
+enum JSHttpRedirectFlag {
+ /**
+ * Handle redirects transparently.
+ */
+ JS_HTTP_REDIRECT_TRANSPARENT = 0,
+ /**
+ * Redirect status codes are returned to the client.
+ * The client can choose to follow them manually (or not).
+ */
+ JS_HTTP_REDIRECT_MANUAL = 1,
+ /**
+ * All redirect status codes result in an error.
+ */
+ JS_HTTP_REDIRECT_ERROR = 2,
+};
+
+/**
+ * Info needed to start a new HTTP request.
+ */
+struct JSHttpRequestInfo {
+ /**
+ * Callback called with the response for the request.
+ */
+ JSHttpResponseCb response_cb;
+
+ /**
+ * Closure for response_cb.
+ */
+ void *response_cb_cls;
+
+ /**
+ * Request URL.
+ */
+ const char *url;
+
+ /**
+ * Request method.
+ */
+ const char *method;
+
+ int num_request_headers;
+
+ /**
+ * Array of `num_request_headers` request headers.
+ */
+ char **request_headers;
+
+ /**
+ * 0: Handle redirects transparently.
+ * 1: Handle redirects manually.
+ * 2: Redirects result in an error.
+ */
+ enum JSHttpRedirectFlag redirect;
+
+ /**
+ * Request timeout in milliseconds.
+ *
+ * When 0 is specified, the timeout is the default request
+ * timeout for the platform.
+ *
+ * When -1 is specified, there is no timeout. This might not be
+ * supported on all platforms.
+ */
+ int timeout_ms;
+
+ /**
+ * Enable debug output for this request.
+ */
+ int debug;
+
+ /**
+ * Request body or NULL.
+ */
+ void *req_body;
+
+ /**
+ * Length or request body or 0.
+ */
+ size_t req_body_len;
+};
+
+/**
+ * Contents of an HTTP response.
+ */
+struct JSHttpResponseInfo {
+
+ /**
+ * Request that this is a response to.
+ *
+ * (Think of the request ID like a file descriptor number).
+ */
+ int request_id;
+
+ /**
+ * HTTP response status code or 0 on error.
+ */
+ int status;
+
+ /**
+ * When status is 0, error message.
+ */
+ char *errmsg;
+
+ /**
+ * Array of `num_response_headers` response headers.
+ */
+ char **response_headers;
+
+ /**
+ * Number of response headers.
+ */
+ int num_response_headers;
+
+ /**
+ * Response body or NULL.
+ */
+ void *body;
+
+ /**
+ * Length of the response body or 0.
+ */
+ size_t body_len;
+};
+
+/**
+ * Callback called when an HTTP response has arrived.
+ *
+ * IMPORTANT: May be called from an arbitrary thread.
+ */
+typedef void (*JSHttpResponseCb)(void *cls, struct JSHttpResponseInfo *resp);
+
+/**
+ * Function to create a new HTTP fetch request.
+ * The request can still be configured until it is started.
+ * An identifier for the request will be written to @a handle.
+ *
+ * @return negative number on error, positive request_id on success
+ */
+typedef int (*JSHttpReqCreateFn)(void *cls, struct JSHttpRequestInfo *req_info);
+
+/**
+ * Cancel a request. The request_id will become invalid
+ * and the callback won't be called with request_id.
+ */
+typedef int (*JSHttpReqCancelFn)(void *cls, int request_id);
+
+struct JSHttpClientImplementation {
+ /**
+ * Opaque closure passed to client functions.
+ */
+ void *cls;
+ JSHttpReqCreateFn req_create;
+ JSHttpReqCancelFn req_cancel;
+};
+
+
+struct JSHttpClientImplementation *
+js_curl_http_client_create(void);
+
+void
+js_curl_http_client_destroy(struct JSHttpClientImplementation *impl);
+
+#endif /* _QUICKJS_HTTP_H */
+
+/**
+ * Opaque handle to a Taler wallet-core instance.
+ */
+struct TALER_WALLET_Instance;
+
+/**
+ * Handler for messages from the wallet.
+ *
+ * @param handler_p opaque closure for the message handler
+ * @param message message from wallet-core as a JSON string
+ */
+typedef void (*TALER_WALLET_MessageHandlerFn)(void *handler_p, const char *message);
+
+enum TALER_WALLET_LogLevel {
+ TALER_WALLET_LOG_TRACE = 1,
+ TALER_WALLET_LOG_INFO = 2,
+ TALER_WALLET_LOG_MESSAGE = 3,
+ TALER_WALLET_LOG_WARN = 4,
+ TALER_WALLET_LOG_ERROR = 5
+};
+
+/**
+ * Handler for log message from wallet-core.
+ *
+ * @param log_p opaque closure for the log handler
+ * @param level log level of the log message
+ * @param tag log tag (usually the file from which the message gets logged)
+ * @param msg the log message
+ */
+typedef void (*TALER_WALLET_LogHandlerFn)(void *log_p,
+ enum TALER_WALLET_LogLevel level,
+ const char *tag,
+ const char *msg);
+
+/**
+ * Create a new wallet-core instance..
+ */
+struct TALER_WALLET_Instance *
+TALER_WALLET_create(void);
+
+/**
+ * Set a handler for notification and response messages.
+ * Must be called before the wallet runs.
+ *
+ * Caution: The handler will be called from a different thread.
+ */
+void
+TALER_WALLET_set_message_handler(struct TALER_WALLET_Instance *twi,
+ TALER_WALLET_MessageHandlerFn handler_f,
+ void *handler_p);
+
+
+/**
+ * Set a handler for log messages from wallet-core.
+ * Must be called before the wallet runs.
+ *
+ * Caution: The log message handler will be called from a different thread.
+ */
+void
+TALER_WALLET_set_log_handler(struct TALER_WALLET_Instance *twi,
+ TALER_WALLET_LogHandlerFn handler_f,
+ void *handler_p);
+
+
+/**
+ * Set/override the JS file with the wallet-core implementation.
+ * Must be called before the wallet runs.
+ */
+// FIXME: Not implemented!
+//void
+//TALER_WALLET_set_jsfile(struct TALER_WALLET_Instance *twi,
+// const char *filename);
+
+/**
+ * Send a message to wallet-core.
+ *
+ * Responses will be sent asynchronously to the message handler
+ * set with #TALER_WALLET_set_message_handler.
+ */
+int
+TALER_WALLET_send_request(struct TALER_WALLET_Instance *twi,
+ const char *request);
+
+/**
+ * Run wallet-core in a thread.
+ *
+ * This function creates a new thread and returns immediately.
+ *
+ * Returns 0 on success or a non-zero error code otherwise.
+ */
+int
+TALER_WALLET_run(struct TALER_WALLET_Instance *twi);
+
+/**
+ * Block until the wallet returns.
+ */
+void
+TALER_WALLET_join(struct TALER_WALLET_Instance *twi);
+
+/**
+ * Destroy the wallet handle and free resources associated with it.
+ *
+ * Note that for a graceful shutdown of the wallet,
+ * an appropriate shutdown message should be sent first,
+ * and destroy() should only be called after the wallet has
+ * sent a response to the shutdown message.
+ */
+//void
+//TALER_WALLET_destroy(struct TALER_WALLET_Instance *twi);
+
+/**
+ * Handler for messages that should be logged.
+ *
+ * @param stream NOT YET IMPLEMENTED: indicator for the stream that
+ * the message is coming from,
+ */
+typedef void (*TALER_LogFn)(void *cls, int stream, const char *msg);
+
+/**
+ * Redirect stderr and stdout to a function.
+ *
+ * Workaround for platforms where stderr is not visible in logs.
+ *
+ * @return 0 on success, error code otherwise
+ */
+int
+TALER_start_redirect_std(TALER_LogFn logfn, void *cls);
+
+
+/**
+ * Set the HTTP client implementation to be used by the wallet.
+ *
+ * @param instance wallet-core instance
+ * @param HTTP client implementation
+ */
+void
+TALER_set_http_client_implementation(struct TALER_WALLET_Instance *instance,
+ struct JSHttpClientImplementation *impl);
+
+#endif /*_TALER_WALLET_LIB_H */
diff --git a/xcode/FTalerWalletcore-Bridging-Header.h b/xcode/FTalerWalletcore-Bridging-Header.h
index 83a9bf6..a93d412 100644
--- a/xcode/FTalerWalletcore-Bridging-Header.h
+++ b/xcode/FTalerWalletcore-Bridging-Header.h
@@ -1 +1 @@
-#import "taler_wallet_core_lib.h"
+#import "taler_wallet_core_lib_preprocessed.h"