commit 764e417aeeeaa1b2f214d6d870147f7f5316d6fe
parent 8329d100a28c53f7a1ba083df7892e80447167b5
Author: Florian Dold <dold@taler.net>
Date: Sun, 9 Aug 2026 21:15:12 +0200
http: make request ownership race-safe
Diffstat:
3 files changed, 263 insertions(+), 63 deletions(-)
diff --git a/quickjs/quickjs-http.c b/quickjs/quickjs-http.c
@@ -31,6 +31,7 @@
struct CurlClientState {
pthread_t thread;
pthread_mutex_t mutex;
+ pthread_cond_t callback_done;
BOOL started;
BOOL stopped;
CURLSH *curlsh;
@@ -48,6 +49,9 @@ struct CurlRequestState {
struct list_head link_cancel; /* for cancel_queue */
DynBuf response_data;
BOOL cancelled;
+ BOOL cancel_queued;
+ BOOL added;
+ BOOL callback_active;
CURL *curl;
int request_id;
enum JSHttpRedirectFlag redirect;
@@ -57,8 +61,20 @@ struct CurlRequestState {
struct curl_slist *req_headers;
struct curl_slist *resp_headers;
char *errbuf;
+ size_t response_header_bytes;
};
+#define MAX_HTTP_RESPONSE_SIZE (64U * 1024U * 1024U)
+#define MAX_HTTP_HEADER_SIZE (1024U * 1024U)
+
+static pthread_once_t curl_global_once = PTHREAD_ONCE_INIT;
+static CURLcode curl_global_status = CURLE_FAILED_INIT;
+
+static void init_curl_global_state(void)
+{
+ curl_global_status = curl_global_init(CURL_GLOBAL_DEFAULT);
+}
+
// Must only be called with locked client mutex
static void destroy_curl_request_state(struct CurlRequestState *crs)
{
@@ -66,7 +82,15 @@ static void destroy_curl_request_state(struct CurlRequestState *crs)
return;
}
- list_del(&crs->link_req);
+ if (crs->link_add.prev) {
+ list_del(&crs->link_add);
+ }
+ if (crs->link_cancel.prev) {
+ list_del(&crs->link_cancel);
+ }
+ if (crs->link_req.prev) {
+ list_del(&crs->link_req);
+ }
curl_slist_free_all(crs->req_headers);
curl_slist_free_all(crs->resp_headers);
dbuf_free(&crs->response_data);
@@ -145,6 +169,9 @@ done:
pthread_mutex_lock(&ccs->mutex);
cancelled = crs->cancelled;
+ if (!cancelled) {
+ crs->callback_active = TRUE;
+ }
pthread_mutex_unlock(&ccs->mutex);
if (cancelled == FALSE) {
@@ -152,10 +179,12 @@ done:
crs->response_cb(crs->response_cb_cls, &hri);
}
- free(headers);
pthread_mutex_lock(&ccs->mutex);
+ crs->callback_active = FALSE;
+ pthread_cond_broadcast(&ccs->callback_done);
destroy_curl_request_state(crs);
pthread_mutex_unlock(&ccs->mutex);
+ free(headers);
return NULL;
}
@@ -165,13 +194,26 @@ static size_t curl_header_callback(char *buffer, size_t size,
struct CurlRequestState *crs = userdata;
size_t sz = size * nitems;
char *hval;
+ struct curl_slist *new_headers;
+
+ if (size != 0 && sz / size != nitems) {
+ return 0;
+ }
+ if (sz > MAX_HTTP_HEADER_SIZE - crs->response_header_bytes) {
+ return 0;
+ }
hval = strndup(buffer, sz);
if (!hval) {
return 0;
}
- crs->resp_headers = curl_slist_append(crs->resp_headers, hval);
+ new_headers = curl_slist_append(crs->resp_headers, hval);
free(hval);
+ if (!new_headers) {
+ return 0;
+ }
+ crs->resp_headers = new_headers;
+ crs->response_header_bytes += sz;
return sz;
}
@@ -181,6 +223,12 @@ 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 (size != 0 && realsize / size != nmemb) {
+ return 0;
+ }
+ if (realsize > MAX_HTTP_RESPONSE_SIZE - rctx->response_data.size) {
+ return 0;
+ }
if (0 != dbuf_put(&rctx->response_data, data, realsize)) {
return 0;
}
@@ -197,13 +245,20 @@ create_impl(void *cls, struct JSHttpRequestInfo *req_info)
CURL *curl;
BOOL debug = req_info->debug > 0;
const char *method = req_info->method;
+ struct curl_slist *new_headers;
+
+ pthread_mutex_lock(&ccs->mutex);
+ if (ccs->stopped) {
+ pthread_mutex_unlock(&ccs->mutex);
+ return -1;
+ }
+ pthread_mutex_unlock(&ccs->mutex);
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;
@@ -215,13 +270,17 @@ create_impl(void *cls, struct JSHttpRequestInfo *req_info)
dbuf_init(&crs->response_data);
curl = curl_easy_init();
+ if (!curl) {
+ goto error;
+ }
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_PROTOCOLS_STR, "http,https");
+ curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "http,https");
+ curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 10L);
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);
@@ -293,18 +352,32 @@ create_impl(void *cls, struct JSHttpRequestInfo *req_info)
if (req_info->request_headers != NULL) {
char **h = req_info->request_headers;
while (*h) {
- crs->req_headers = curl_slist_append(crs->req_headers, *h);
+ new_headers = curl_slist_append(crs->req_headers, *h);
+ if (!new_headers) {
+ goto error;
+ }
+ crs->req_headers = new_headers;
h++;
}
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, crs->req_headers);
pthread_mutex_lock(&ccs->mutex);
+ if (ccs->stopped) {
+ pthread_mutex_unlock(&ccs->mutex);
+ goto error;
+ }
+ if (ccs->last_request_id == INT_MAX) {
+ ccs->last_request_id = 1;
+ } else {
+ ccs->last_request_id++;
+ }
+ crs->request_id = ccs->last_request_id;
list_add_tail(&crs->link_add, &ccs->add_queue);
list_add_tail(&crs->link_req, &ccs->request_list);
pthread_mutex_unlock(&ccs->mutex);
- curl_multi_wakeup(ccs->curlm);
+ (void) curl_multi_wakeup(ccs->curlm);
return crs->request_id;
error:
@@ -316,6 +389,8 @@ error:
if (crs->curl) {
curl_easy_cleanup(crs->curl);
}
+ curl_slist_free_all(crs->req_headers);
+ curl_slist_free_all(crs->resp_headers);
free(crs);
}
return -1;
@@ -331,14 +406,27 @@ destroy_impl(void *cls, int request_id)
list_for_each(el, &ccs->request_list) {
struct CurlRequestState *crs = list_entry(el, struct CurlRequestState, link_req);
- if (crs->request_id == request_id && !crs->cancelled) {
- list_add_tail(&crs->link_cancel, &ccs->cancel_queue);
+ if (crs->request_id == request_id) {
+ crs->cancelled = TRUE;
+ while (crs->callback_active &&
+ !pthread_equal(pthread_self(), ccs->thread)) {
+ pthread_cond_wait(&ccs->callback_done, &ccs->mutex);
+ pthread_mutex_unlock(&ccs->mutex);
+ return destroy_impl(cls, request_id);
+ }
+ if (!crs->callback_active && !crs->cancel_queued) {
+ crs->cancel_queued = TRUE;
+ list_add_tail(&crs->link_cancel, &ccs->cancel_queue);
+ }
+ break;
}
}
pthread_mutex_unlock(&ccs->mutex);
- curl_multi_wakeup(ccs->curlm);
+ if (ccs->curlm) {
+ (void) curl_multi_wakeup(ccs->curlm);
+ }
return 0;
}
@@ -385,31 +473,45 @@ curl_multi_thread_run(void *cls)
pthread_mutex_lock(&ccs->mutex);
list_for_each_safe(el, el1, &ccs->add_queue) {
struct CurlRequestState *crs = list_entry(el, struct CurlRequestState, link_add);
- curl_multi_add_handle(ccs->curlm, crs->curl);
+ if (CURLM_OK == curl_multi_add_handle(ccs->curlm, crs->curl)) {
+ crs->added = TRUE;
+ } else {
+ crs->cancelled = TRUE;
+ if (!crs->cancel_queued) {
+ crs->cancel_queued = TRUE;
+ list_add_tail(&crs->link_cancel, &ccs->cancel_queue);
+ }
+ }
list_del(el);
}
pthread_mutex_unlock(&ccs->mutex);
+ // Process finished requests before cancellation so CURLMSG_DONE
+ // never references an easy handle that cancellation already freed.
+ int msgq = 0;
+ while ((m = curl_multi_info_read(ccs->curlm, &msgq)) != NULL) {
+ if (m->msg == CURLMSG_DONE) {
+ CURL *e = m->easy_handle;
+ curl_multi_remove_handle(ccs->curlm, e);
+ handle_done(e, m->data.result);
+ }
+ }
+
// Cancel requests in queue
pthread_mutex_lock(&ccs->mutex);
list_for_each_safe(el, el1, &ccs->cancel_queue) {
struct CurlRequestState *crs = list_entry(el, struct CurlRequestState, link_cancel);
- curl_multi_remove_handle(ccs->curlm, crs->curl);
- crs->cancelled = TRUE;
- list_del(el);
+ if (crs->added) {
+ curl_multi_remove_handle(ccs->curlm, crs->curl);
+ }
+ destroy_curl_request_state(crs);
}
pthread_mutex_unlock(&ccs->mutex);
-
- // Process finished request
- 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);
+ } while (m);
}
+ pthread_mutex_lock(&ccs->mutex);
+ ccs->stopped = TRUE;
+ pthread_mutex_unlock(&ccs->mutex);
if (CURLM_OK != curl_multi_cleanup(ccs->curlm)) {
fprintf(stderr, "warning: curl_multi_cleanup failed\n");
}
@@ -426,15 +528,18 @@ js_curl_http_client_create()
struct CurlClientState *ccs = NULL;
int res;
- ccs = malloc(sizeof *ccs);
+ if (0 != pthread_once(&curl_global_once, init_curl_global_state) ||
+ CURLE_OK != curl_global_status) {
+ return NULL;
+ }
+
+ ccs = calloc(1, sizeof *ccs);
if (!ccs) {
goto error;
}
pthread_mutex_init(&ccs->mutex, NULL);
- ccs->started = FALSE;
- ccs->stopped = FALSE;
- ccs->last_request_id = 0;
+ pthread_cond_init(&ccs->callback_done, NULL);
ccs->curlsh = curl_share_init();
if (!ccs->curlsh) {
goto error;
@@ -460,17 +565,22 @@ js_curl_http_client_create()
impl->cls = ccs;
res = pthread_create(&ccs->thread, NULL, &curl_multi_thread_run, ccs);
- ccs->started = TRUE;
-
if (0 != res) {
goto error;
}
+ ccs->started = TRUE;
return impl;
error:
if (ccs) {
- curl_share_cleanup(ccs->curlsh);
- curl_multi_cleanup(ccs->curlm);
+ if (ccs->curlsh) {
+ curl_share_cleanup(ccs->curlsh);
+ }
+ if (ccs->curlm) {
+ curl_multi_cleanup(ccs->curlm);
+ }
+ pthread_cond_destroy(&ccs->callback_done);
+ pthread_mutex_destroy(&ccs->mutex);
free(ccs);
}
if (impl) {
@@ -506,6 +616,7 @@ destroy_client_state(struct CurlClientState *ccs)
destroy_curl_request_state(crs);
}
pthread_mutex_unlock(&ccs->mutex);
+ pthread_cond_destroy(&ccs->callback_done);
pthread_mutex_destroy(&ccs->mutex);
free(ccs);
}
diff --git a/quickjs/quickjs-http.h b/quickjs/quickjs-http.h
@@ -124,7 +124,7 @@ struct JSHttpRequestInfo {
/**
* Length or request body or 0.
*/
- uint32_t req_body_len;
+ size_t req_body_len;
};
/**
@@ -167,17 +167,10 @@ struct JSHttpResponseInfo {
/**
* Length of the response body or 0.
*/
- uint32_t body_len;
+ 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.
@@ -187,8 +180,9 @@ typedef void (*JSHttpResponseCb)(void *cls, struct JSHttpResponseInfo *resp);
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.
+ * Cancel a request. The request_id will become invalid. When this function
+ * returns, the response callback is no longer running and will not be called
+ * in the future for this request.
*/
typedef int (*JSHttpReqCancelFn)(void *cls, int request_id);
diff --git a/quickjs/quickjs-libc.c b/quickjs/quickjs-libc.c
@@ -2319,7 +2319,8 @@ int expect_property_str_bool(JSContext *ctx, JSValueConst this_val, const char *
return bool_val;
}
-static void free_http_request_context(HttpRequestContext *req_context)
+static void free_http_request_context(HttpRequestContext *req_context,
+ BOOL cancel_native)
{
JSContext *ctx;
JSThreadState *ts;
@@ -2329,8 +2330,10 @@ static void free_http_request_context(HttpRequestContext *req_context)
}
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 (cancel_native && ts->http_client_impl && req_context->request_id > 0) {
+ ts->http_client_impl->req_cancel(ts->http_client_impl->cls,
+ req_context->request_id);
+ }
JS_FreeValue(ctx, req_context->resolve_func);
JS_FreeValue(ctx, req_context->reject_func);
if (NULL != req_context->link.prev) {
@@ -2386,6 +2389,9 @@ static void handle_http_resp(void *cls, struct JSHttpResponseInfo *resp_info)
int num_headers;
num_headers = resp_info->num_response_headers;
+ if (num_headers < 0 || num_headers > 10000) {
+ goto fail;
+ }
msg->response_headers = malloc((num_headers + 1) * sizeof (char *));
if (!msg->response_headers) {
@@ -2411,6 +2417,9 @@ static void handle_http_resp(void *cls, struct JSHttpResponseInfo *resp_info)
}
if (resp_info->body_len > 0) {
+ if (!resp_info->body) {
+ goto fail;
+ }
msg->body = malloc(resp_info->body_len);
if (!msg->body) {
goto fail;
@@ -2451,11 +2460,39 @@ static JSValue cancel_http_req(JSContext *ctx, JSValueConst this_val,
JSThreadState *ts = JS_GetRuntimeOpaque(rt);
int req_id;
int ret;
+ struct list_head *el;
+ HttpRequestContext *req_context = NULL;
+
+ if (0 != JS_ToInt32(ctx, &req_id, func_data[0])) {
+ return JS_EXCEPTION;
+ }
- JS_ToInt32(ctx, &req_id, func_data[0]);
+ list_for_each(el, &ts->http_requests) {
+ HttpRequestContext *candidate =
+ list_entry(el, HttpRequestContext, link);
+ if (candidate->request_id == req_id) {
+ req_context = candidate;
+ break;
+ }
+ }
+ if (!req_context) {
+ return JS_NewInt32(ctx, 0);
+ }
// cancel HTTP request
ret = ts->http_client_impl->req_cancel(ts->http_client_impl->cls, req_id);
+ if (ret == 0) {
+ JSValue error = JS_NewError(ctx);
+ JSValue result;
+
+ JS_DefinePropertyValueStr(ctx, error, "message",
+ JS_NewString(ctx, "HTTP request cancelled"),
+ JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+ result = JS_Call(ctx, req_context->reject_func, JS_UNDEFINED, 1, &error);
+ JS_FreeValue(ctx, result);
+ JS_FreeValue(ctx, error);
+ free_http_request_context(req_context, FALSE);
+ }
return JS_NewInt32(ctx, ret);
}
@@ -2483,11 +2520,18 @@ static char **gather_http_headers(JSContext *ctx, JSValueConst js_headers)
return NULL;
}
if (0 != JS_ToUint32(ctx, &length, length_prop)) {
+ JS_FreeValue(ctx, length_prop);
return NULL;
}
JS_FreeValue(ctx, length_prop);
- headers = js_mallocz(ctx, (length + 1) * sizeof (char *));
+#if SIZE_MAX <= UINT32_MAX
+ if (length > SIZE_MAX / sizeof(char *) - 1) {
+ JS_ThrowRangeError(ctx, "too many HTTP headers");
+ goto exception;
+ }
+#endif
+ headers = js_mallocz(ctx, ((size_t) length + 1) * sizeof (char *));
if (!headers) {
goto exception;
}
@@ -2505,6 +2549,8 @@ static char **gather_http_headers(JSContext *ctx, JSValueConst js_headers)
}
hval = js_strdup(ctx, cstr);
if (!hval) {
+ JS_FreeCString(ctx, cstr);
+ JS_FreeValue(ctx, item);
goto exception;
}
JS_FreeCString(ctx, cstr);
@@ -2531,6 +2577,10 @@ static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val,
JSRuntime *rt = JS_GetRuntime(ctx);
JSThreadState *ts = JS_GetRuntimeOpaque(rt);
JSValue resolving_funs[2];
+ JSValue promise = JS_UNDEFINED;
+ JSValue requestId = JS_UNDEFINED;
+ JSValue cancelCls = JS_UNDEFINED;
+ JSValue cancelFn = JS_UNDEFINED;
JSValue options = JS_UNINITIALIZED;
JSValue method = JS_UNINITIALIZED;
const char *method_str = NULL;
@@ -2540,6 +2590,7 @@ static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val,
BOOL debug = FALSE;
int redirect = 0;
int ret;
+ BOOL request_created = FALSE;
if (NULL == ts->http_client_impl) {
JS_ThrowInternalError(ctx, "no HTTP client implementation available");
@@ -2547,7 +2598,12 @@ static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val,
}
req_context = js_mallocz(ctx, sizeof *req_context);
+ if (!req_context) {
+ goto exception;
+ }
req_context->ctx = ctx;
+ req_context->resolve_func = JS_UNDEFINED;
+ req_context->reject_func = JS_UNDEFINED;
req_url = JS_ToCString(ctx, argv[0]);
if (!req_url) {
@@ -2561,7 +2617,17 @@ static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val,
int has_prop_redirect;
method = JS_GetPropertyStr(ctx, options, "method");
+ if (JS_IsException(method)) {
+ goto exception;
+ }
+ if (JS_IsUndefined(method)) {
+ JS_FreeValue(ctx, method);
+ method = JS_NewString(ctx, "get");
+ }
debug = expect_property_str_bool(ctx, options, "debug");
+ if (debug < 0) {
+ goto exception;
+ }
has_prop_redirect = JS_HasPropertyStr(ctx, options, "redirect");
if (has_prop_redirect < 0) {
@@ -2574,13 +2640,16 @@ static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val,
goto exception;
}
if (JS_ToInt32(ctx, &redir_num, redir_val)) {
+ JS_FreeValue(ctx, redir_val);
goto exception;
}
if (redir_num < 0 || redir_num > JS_HTTP_REDIRECT_ERROR) {
+ JS_FreeValue(ctx, redir_val);
JS_ThrowTypeError(ctx, "redirect option out of range");
goto exception;
}
redirect = redir_num;
+ JS_FreeValue(ctx, redir_val);
}
} else {
JS_ThrowTypeError(ctx, "invalid options");
@@ -2622,11 +2691,13 @@ static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val,
if (!(JS_IsNull(data) || JS_IsUndefined(data))) {
data_ptr = JS_GetArrayBuffer(ctx, &data_len, data);
if (!data_ptr) {
+ JS_FreeValue(ctx, data);
goto exception;
}
}
req.req_body = data_ptr;
req.req_body_len = data_len;
+ JS_FreeValue(ctx, data);
}
}
@@ -2638,43 +2709,64 @@ static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val,
req.redirect = redirect;
req.response_cb = &handle_http_resp;
req.response_cb_cls = req_context;
+
+ promise = JS_NewPromiseCapability(ctx, resolving_funs);
+ if (JS_IsException(promise)) {
+ goto exception;
+ }
+ req_context->resolve_func = resolving_funs[0];
+ req_context->reject_func = resolving_funs[1];
+
ret = ts->http_client_impl->req_create(ts->http_client_impl->cls, &req);
if (ret < 0) {
JS_ThrowInternalError(ctx, "failed to create request");
goto exception;
}
+ request_created = TRUE;
+ req_context->request_id = ret;
list_add_tail(&req_context->link, &ts->http_requests);
// requestId: number
- JSValue requestId = JS_NewInt32(ctx, ret);
+ requestId = JS_NewInt32(ctx, ret);
- // promise: Promise<Response>
- JSValue promise = JS_NewPromiseCapability(ctx, resolving_funs);
- if (JS_IsException(promise)) {
+ // cancelFn: () => void
+ cancelCls = JS_NewInt32(ctx, ret);
+ cancelFn = JS_NewCFunctionData(ctx, &cancel_http_req, 0, 0, 1, &cancelCls);
+ JS_FreeValue(ctx, cancelCls);
+ cancelCls = JS_UNDEFINED;
+ if (JS_IsException(cancelFn)) {
goto exception;
}
- req_context->request_id = ret;
- req_context->resolve_func = resolving_funs[0];
- req_context->reject_func = resolving_funs[1];
-
- // cancelFn: () => void
- JSValue cancelCls = JS_NewInt32(ctx, ret);
- JSValue cancelFn = JS_NewCFunctionData(ctx, &cancel_http_req, 0, 0, 1, &cancelCls);
ret_val = JS_NewObject(ctx);
+ if (JS_IsException(ret_val)) {
+ goto exception;
+ }
JS_SetPropertyStr(ctx, ret_val, "requestId", requestId);
+ requestId = JS_UNDEFINED;
JS_SetPropertyStr(ctx, ret_val, "promise", promise);
+ promise = JS_UNDEFINED;
JS_SetPropertyStr(ctx, ret_val, "cancelFn", cancelFn);
+ cancelFn = JS_UNDEFINED;
done:
free_http_headers(ctx, req.request_headers);
JS_FreeValue(ctx, method);
JS_FreeCString(ctx, req_url);
JS_FreeCString(ctx, method_str);
+ JS_FreeValue(ctx, promise);
+ JS_FreeValue(ctx, requestId);
+ JS_FreeValue(ctx, cancelCls);
+ JS_FreeValue(ctx, cancelFn);
return ret_val;
exception:
+ if (req_context) {
+ free_http_request_context(req_context, request_created);
+ req_context = NULL;
+ }
+ JS_FreeValue(ctx, ret_val);
ret_val = JS_EXCEPTION;
goto done;
@@ -3123,7 +3215,7 @@ static int handle_http_message(JSRuntime *rt, JSContext *ctx)
struct list_head *req_el;
JSHttpMessage *msg;
JSValue obj, func, retval;
- HttpRequestContext *request_ctx;
+ HttpRequestContext *request_ctx = NULL;
pthread_mutex_lock(&hp->mutex);
if (!list_empty(&hp->msg_queue)) {
@@ -3183,12 +3275,15 @@ static int handle_http_message(JSRuntime *rt, JSContext *ctx)
atom_message = JS_NewAtom(ctx, "message");
obj = JS_NewError(ctx);
JS_DefinePropertyValue(ctx, obj, atom_message,
- JS_NewString(ctx, msg->errmsg),
+ JS_NewString(ctx, msg->errmsg ? msg->errmsg :
+ "HTTP request failed"),
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);
+ JS_FreeValue(ctx, obj);
}
+ free_http_request_context(request_ctx, FALSE);
break;
}
}
@@ -5110,7 +5205,7 @@ void js_std_free_handlers(JSRuntime *rt)
#ifndef NO_HTTP
list_for_each_safe(el, el1, &ts->http_requests) {
HttpRequestContext *request_ctx = list_entry(el, HttpRequestContext, link);
- free_http_request_context(request_ctx);
+ free_http_request_context(request_ctx, TRUE);
}
#endif