quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

quickjs-http.c (20500B)


      1 /*
      2  This file is part of GNU Taler
      3  Copyright (C) 2024 Taler Systems SA
      4 
      5  GNU Taler is free software; you can redistribute it and/or modify it under the
      6  terms of the GNU Affero General Public License as published by the Free Software
      7  Foundation; either version 3, or (at your option) any later version.
      8 
      9  GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13  You should have received a copy of the GNU Affero General Public License along with
     14  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 #include <stdlib.h>
     18 #include <pthread.h>
     19 #include <stdio.h>
     20 #include <curl/curl.h>
     21 #include <arpa/inet.h>
     22 #include <strings.h>
     23 #include <string.h>
     24 #include <assert.h>
     25 #include <unistd.h>
     26 
     27 #include "curl/multi.h"
     28 #include "cutils.h"
     29 #include "quickjs-http.h"
     30 #include "list.h"
     31 
     32 struct CurlClientState {
     33     pthread_t thread;
     34     pthread_mutex_t mutex;
     35     pthread_cond_t callback_done;
     36     BOOL started;
     37     BOOL stopped;
     38     CURLSH *curlsh;
     39     CURLM *curlm;
     40     int last_request_id;
     41     struct list_head request_list; /* list of CurlRequestState.link */
     42     struct list_head add_queue;    /* multi_add_handle queue */
     43     struct list_head cancel_queue; /* multi_remove_handle queue */
     44 };
     45 
     46 struct CurlRequestState {
     47     struct CurlClientState *ccs;
     48     struct list_head link_req;    /* for request_list */
     49     struct list_head link_add;    /* for add_queue */
     50     struct list_head link_cancel; /* for cancel_queue */
     51     DynBuf response_data;
     52     BOOL cancelled;
     53     BOOL cancel_queued;
     54     BOOL added;
     55     BOOL callback_active;
     56     CURL *curl;
     57     int request_id;
     58     enum JSHttpRedirectFlag redirect;
     59     JSHttpResponseCb response_cb;
     60     void *response_cb_cls;
     61     // Request headers
     62     struct curl_slist *req_headers;
     63     struct curl_slist *resp_headers;
     64     char *errbuf;
     65     size_t response_header_bytes;
     66 };
     67 
     68 #define MAX_HTTP_RESPONSE_SIZE (64U * 1024U * 1024U)
     69 #define MAX_HTTP_HEADER_SIZE (1024U * 1024U)
     70 
     71 static pthread_once_t curl_global_once = PTHREAD_ONCE_INIT;
     72 static CURLcode curl_global_status = CURLE_FAILED_INIT;
     73 
     74 static void init_curl_global_state(void)
     75 {
     76     curl_global_status = curl_global_init(CURL_GLOBAL_DEFAULT);
     77 }
     78 
     79 static const char *
     80 find_system_ca_bundle(void)
     81 {
     82     static const char * const candidates[] = {
     83         "/etc/ssl/certs/ca-certificates.crt",
     84         "/etc/pki/tls/certs/ca-bundle.crt",
     85         "/usr/share/ssl/certs/ca-bundle.crt",
     86         "/usr/local/share/certs/ca-root-nss.crt",
     87         "/etc/ssl/cert.pem",
     88         "/var/lib/ca-certificates/ca-bundle.pem",
     89         NULL,
     90     };
     91 
     92     for (const char * const *candidate = candidates;
     93          NULL != *candidate;
     94          candidate++) {
     95         if (0 == access(*candidate, R_OK)) {
     96             return *candidate;
     97         }
     98     }
     99     return NULL;
    100 }
    101 
    102 /**
    103  * libcurl itself does not read the curl command-line tool's CA environment
    104  * variables.  Our minimal Meson curl build also has no configured default CA
    105  * location, so reproduce curl's environment handling and then fall back to
    106  * its standard Unix bundle search order.
    107  */
    108 static CURLcode
    109 configure_ca_locations(CURL *curl)
    110 {
    111     const char *ca_bundle = getenv("CURL_CA_BUNDLE");
    112     const char *ca_path = NULL;
    113 
    114     if (NULL == ca_bundle || '\0' == ca_bundle[0]) {
    115         ca_bundle = getenv("SSL_CERT_FILE");
    116         ca_path = getenv("SSL_CERT_DIR");
    117         if (NULL != ca_bundle && '\0' == ca_bundle[0]) {
    118             ca_bundle = NULL;
    119         }
    120         if (NULL != ca_path && '\0' == ca_path[0]) {
    121             ca_path = NULL;
    122         }
    123     }
    124     if (NULL == ca_bundle && NULL == ca_path) {
    125         ca_bundle = find_system_ca_bundle();
    126     }
    127     if (NULL != ca_bundle) {
    128         CURLcode result = curl_easy_setopt(curl, CURLOPT_CAINFO, ca_bundle);
    129 
    130         if (CURLE_OK != result) {
    131             return result;
    132         }
    133     }
    134     if (NULL != ca_path) {
    135         return curl_easy_setopt(curl, CURLOPT_CAPATH, ca_path);
    136     }
    137     return CURLE_OK;
    138 }
    139 
    140 // Must only be called with locked client mutex
    141 static void destroy_curl_request_state(struct CurlRequestState *crs)
    142 {
    143     if (!crs) {
    144         return;
    145     }
    146 
    147     if (crs->link_add.prev) {
    148         list_del(&crs->link_add);
    149     }
    150     if (crs->link_cancel.prev) {
    151         list_del(&crs->link_cancel);
    152     }
    153     if (crs->link_req.prev) {
    154         list_del(&crs->link_req);
    155     }
    156     curl_slist_free_all(crs->req_headers);
    157     curl_slist_free_all(crs->resp_headers);
    158     dbuf_free(&crs->response_data);
    159     if (crs->curl) {
    160         curl_easy_cleanup(crs->curl);
    161         crs->curl = NULL;
    162     }
    163     free(crs->errbuf);
    164     free(crs);
    165 }
    166 
    167 static void *
    168 handle_done(CURL *curl, CURLcode res)
    169 {
    170     struct CurlRequestState *crs = NULL;
    171     struct CurlClientState *ccs = NULL;
    172     struct JSHttpResponseInfo hri = { 0 };
    173     long resp_code;
    174     char **headers = NULL;
    175     BOOL cancelled;
    176 
    177     curl_easy_getinfo(curl, CURLINFO_PRIVATE, &crs);
    178     ccs = crs->ccs;
    179 
    180     hri.request_id = crs->request_id;
    181 
    182     if (CURLE_OK == res) {
    183         int num_headers = 0;
    184         int i;
    185         struct curl_slist *sl = crs->resp_headers;
    186         char *url = NULL;
    187 
    188         curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &url);
    189 
    190         if (crs->redirect == JS_HTTP_REDIRECT_ERROR && NULL != url) {
    191             hri.status = 0;
    192             hri.errmsg = crs->errbuf;
    193             strncpy(crs->errbuf, "Got redirect status, but redirects are not allowed for this request", CURL_ERROR_SIZE);
    194             goto done;
    195         }
    196 
    197         while (sl != NULL) {
    198             if (NULL != strchr(sl->data, ':')) {
    199                 num_headers++;
    200             }
    201             sl = sl->next;
    202         }
    203 
    204         headers = malloc((num_headers + 1) * sizeof(char *));
    205         if (!headers) {
    206             hri.status = 0;
    207             goto done;
    208         }
    209         memset(headers, 0, (num_headers + 1) * sizeof (char *));
    210         sl = crs->resp_headers;
    211         i = 0;
    212         while (sl != NULL) {
    213             if (NULL != strchr(sl->data, ':')) {
    214                 headers[i] = sl->data;
    215                 i++;
    216             }
    217           sl = sl->next;
    218         }
    219         curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &resp_code);
    220         hri.status = resp_code;
    221         hri.body = crs->response_data.buf;
    222         hri.body_len = crs->response_data.size;
    223         hri.response_headers = headers;
    224         hri.num_response_headers = num_headers;
    225     } else {
    226         hri.status = 0;
    227         hri.errmsg = crs->errbuf;
    228     }
    229 
    230 done:
    231 
    232     pthread_mutex_lock(&ccs->mutex);
    233     cancelled = crs->cancelled;
    234     if (!cancelled) {
    235         crs->callback_active = TRUE;
    236     }
    237     pthread_mutex_unlock(&ccs->mutex);
    238 
    239     if (cancelled == FALSE) {
    240       // FIXME: What if this CB somehow destroys the client?
    241       crs->response_cb(crs->response_cb_cls, &hri);
    242     }
    243 
    244     pthread_mutex_lock(&ccs->mutex);
    245     crs->callback_active = FALSE;
    246     pthread_cond_broadcast(&ccs->callback_done);
    247     destroy_curl_request_state(crs);
    248     pthread_mutex_unlock(&ccs->mutex);
    249     free(headers);
    250     return NULL;
    251 }
    252 
    253 static size_t curl_header_callback(char *buffer, size_t size,
    254                               size_t nitems, void *userdata)
    255 {
    256     struct CurlRequestState *crs = userdata;
    257     size_t sz = size * nitems;
    258     char *hval;
    259     struct curl_slist *new_headers;
    260 
    261     if (size != 0 && sz / size != nitems) {
    262         return 0;
    263     }
    264     if (sz > MAX_HTTP_HEADER_SIZE - crs->response_header_bytes) {
    265         return 0;
    266     }
    267 
    268     hval = strndup(buffer, sz);
    269     if (!hval) {
    270         return 0;
    271     }
    272     new_headers = curl_slist_append(crs->resp_headers, hval);
    273     free(hval);
    274     if (!new_headers) {
    275         return 0;
    276     }
    277     crs->resp_headers = new_headers;
    278     crs->response_header_bytes += sz;
    279     return sz;
    280 }
    281 
    282 
    283 static size_t curl_write_cb(char *data, size_t size, size_t nmemb, void *userp)
    284 {
    285     size_t realsize = size * nmemb;
    286     struct CurlRequestState *rctx = userp;
    287 
    288     if (size != 0 && realsize / size != nmemb) {
    289         return 0;
    290     }
    291     if (realsize > MAX_HTTP_RESPONSE_SIZE - rctx->response_data.size) {
    292         return 0;
    293     }
    294     if (0 != dbuf_put(&rctx->response_data,
    295                       (const uint8_t *) data,
    296                       realsize)) {
    297         return 0;
    298     }
    299 
    300     return realsize;
    301 }
    302 
    303 
    304 static int
    305 create_impl(void *cls, struct JSHttpRequestInfo *req_info)
    306 {
    307     struct CurlClientState *ccs = cls;
    308     struct CurlRequestState *crs;
    309     CURL *curl;
    310     BOOL debug = req_info->debug > 0;
    311     const char *method = req_info->method;
    312     struct curl_slist *new_headers;
    313 
    314     pthread_mutex_lock(&ccs->mutex);
    315     if (ccs->stopped) {
    316         pthread_mutex_unlock(&ccs->mutex);
    317         return -1;
    318     }
    319     pthread_mutex_unlock(&ccs->mutex);
    320 
    321     crs = malloc(sizeof *crs);
    322     if (!crs) {
    323       return -1;
    324     }
    325     memset(crs, 0, sizeof *crs);
    326     crs->ccs = ccs;
    327     crs->response_cb = req_info->response_cb;
    328     crs->response_cb_cls = req_info->response_cb_cls;
    329     crs->errbuf = malloc(CURL_ERROR_SIZE);
    330     if (!crs->errbuf) {
    331         goto error;
    332     }
    333     memset(crs->errbuf, 0, CURL_ERROR_SIZE);
    334     dbuf_init(&crs->response_data);
    335 
    336     curl = curl_easy_init();
    337     if (!curl) {
    338         goto error;
    339     }
    340     crs->curl = curl;
    341     curl_easy_setopt(curl, CURLOPT_PRIVATE, crs);
    342     curl_easy_setopt(curl, CURLOPT_SHARE, ccs->curlsh);
    343     curl_easy_setopt(curl, CURLOPT_URL, req_info->url);
    344     curl_easy_setopt(curl, CURLOPT_USERAGENT, "qtart");
    345     curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, "http,https");
    346     curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "http,https");
    347     curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 10L);
    348     if (CURLE_OK != configure_ca_locations(curl)) {
    349         goto error;
    350     }
    351     curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_callback);
    352     curl_easy_setopt(curl, CURLOPT_HEADERDATA, crs);
    353     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
    354     curl_easy_setopt(curl, CURLOPT_WRITEDATA, crs);
    355 
    356     curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, crs->errbuf);
    357 
    358 #ifdef QTART_INSECURE_SKIP_TLS_VERIFICATION
    359     // This is only a temporary hack to use the libcurl HTTP client implementation
    360     // on platforms (like iOS) where we can't easily access the root store.
    361     // Outside of testing, such platforms should supply a native HTTP client
    362     // implementation and not use the libcurl implementation compiled
    363     // into qtart.
    364     curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
    365     curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
    366 #endif
    367 
    368     if (req_info->timeout_ms < 0) {
    369         curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 0L);
    370     } else if (0 == req_info->timeout_ms) {
    371         // Default timeout of 5 minutes.
    372         curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 5L * 60000L);
    373     } else {
    374         curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, (long) req_info->timeout_ms);
    375     }
    376 
    377     if (debug == TRUE) {
    378         curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
    379     }
    380 
    381     crs->redirect = req_info->redirect;
    382 
    383     switch (req_info->redirect) {
    384       case JS_HTTP_REDIRECT_TRANSPARENT:
    385         curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    386         break;
    387       case JS_HTTP_REDIRECT_MANUAL:
    388         curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
    389         break;
    390       case JS_HTTP_REDIRECT_ERROR:
    391         curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
    392         break;
    393       default:
    394         assert(0);
    395     }
    396 
    397     if (0 == strcasecmp(req_info->method, "get")) {
    398         curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
    399     } else if (0 == strcasecmp(method, "delete")) {
    400         curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
    401         curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    402     } else if (0 == strcasecmp(method, "head")) {
    403         curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
    404     } else if ((0 == strcasecmp(method, "post")) ||
    405                (0 == strcasecmp(method, "put"))) {
    406         curl_easy_setopt(curl, CURLOPT_POST, 1L);
    407         if (0 == strcasecmp(method, "put")) {
    408             curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
    409         }
    410         if (req_info->req_body_len > 0) {
    411             curl_off_t len = req_info->req_body_len;
    412             curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, len);
    413             curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, req_info->req_body);
    414         }
    415     } else {
    416         goto error;
    417     }
    418 
    419     if (req_info->request_headers != NULL) {
    420       char **h = req_info->request_headers;
    421       while (*h) {
    422         new_headers = curl_slist_append(crs->req_headers, *h);
    423         if (!new_headers) {
    424             goto error;
    425         }
    426         crs->req_headers = new_headers;
    427         h++;
    428       }
    429     }
    430     curl_easy_setopt(curl, CURLOPT_HTTPHEADER, crs->req_headers);
    431 
    432     pthread_mutex_lock(&ccs->mutex);
    433     if (ccs->stopped) {
    434         pthread_mutex_unlock(&ccs->mutex);
    435         goto error;
    436     }
    437     if (ccs->last_request_id == INT_MAX) {
    438         ccs->last_request_id = 1;
    439     } else {
    440         ccs->last_request_id++;
    441     }
    442     crs->request_id = ccs->last_request_id;
    443     list_add_tail(&crs->link_add, &ccs->add_queue);
    444     list_add_tail(&crs->link_req, &ccs->request_list);
    445     pthread_mutex_unlock(&ccs->mutex);
    446 
    447     (void) curl_multi_wakeup(ccs->curlm);
    448 
    449     return crs->request_id;
    450 error:
    451     if (crs) {
    452       dbuf_free(&crs->response_data);
    453       if (crs->errbuf) {
    454         free(crs->errbuf);
    455       }
    456       if (crs->curl) {
    457         curl_easy_cleanup(crs->curl);
    458       }
    459       curl_slist_free_all(crs->req_headers);
    460       curl_slist_free_all(crs->resp_headers);
    461       free(crs);
    462     }
    463     return -1;
    464 }
    465 
    466 static int
    467 destroy_impl(void *cls, int request_id)
    468 {
    469     struct list_head *el;
    470     struct CurlClientState *ccs = cls;  
    471 
    472     pthread_mutex_lock(&ccs->mutex);
    473 
    474     list_for_each(el, &ccs->request_list) {
    475         struct CurlRequestState *crs = list_entry(el, struct CurlRequestState, link_req);
    476         if (crs->request_id == request_id) {
    477             crs->cancelled = TRUE;
    478             while (crs->callback_active &&
    479                    !pthread_equal(pthread_self(), ccs->thread)) {
    480                 pthread_cond_wait(&ccs->callback_done, &ccs->mutex);
    481                 pthread_mutex_unlock(&ccs->mutex);
    482                 return destroy_impl(cls, request_id);
    483             }
    484             if (!crs->callback_active && !crs->cancel_queued) {
    485                 crs->cancel_queued = TRUE;
    486                 list_add_tail(&crs->link_cancel, &ccs->cancel_queue);
    487             }
    488             break;
    489         }
    490     }
    491 
    492     pthread_mutex_unlock(&ccs->mutex);
    493 
    494     if (ccs->curlm) {
    495         (void) curl_multi_wakeup(ccs->curlm);
    496     }
    497 
    498     return 0;
    499 }
    500 
    501 /**
    502  * Entry point for the thread that processes HTTP requests with libcurl.
    503  */
    504 static void *
    505 curl_multi_thread_run(void *cls)
    506 {
    507     struct CurlClientState *ccs = cls;
    508     struct list_head *el, *el1;
    509     int still_running;
    510     struct CURLMsg *m;
    511     BOOL stopped;
    512 
    513     while (1) {
    514         CURLMcode mc;
    515 
    516         mc = curl_multi_perform(ccs->curlm, &still_running);
    517 
    518         if (CURLM_OK != mc) {
    519             fprintf(stderr, "curl_multi_perform failed\n");
    520             break;
    521         }
    522 
    523         mc = curl_multi_poll(ccs->curlm, NULL, 0, 1000, NULL);
    524         if (CURLM_OK != mc) {
    525             fprintf(stderr, "curl_multi_poll failed\n");
    526             break;
    527         }
    528 
    529         pthread_mutex_lock(&ccs->mutex);
    530         stopped = ccs->stopped;
    531         pthread_mutex_unlock(&ccs->mutex);
    532 
    533         if (stopped) {
    534             break;
    535         }
    536 
    537         do {
    538 
    539             // Add new requests in queue
    540             pthread_mutex_lock(&ccs->mutex);
    541             list_for_each_safe(el, el1, &ccs->add_queue) {
    542                 struct CurlRequestState *crs = list_entry(el, struct CurlRequestState, link_add);
    543                 if (CURLM_OK == curl_multi_add_handle(ccs->curlm, crs->curl)) {
    544                     crs->added = TRUE;
    545                 } else {
    546                     crs->cancelled = TRUE;
    547                     if (!crs->cancel_queued) {
    548                         crs->cancel_queued = TRUE;
    549                         list_add_tail(&crs->link_cancel, &ccs->cancel_queue);
    550                     }
    551                 }
    552                 list_del(el);
    553             }
    554             pthread_mutex_unlock(&ccs->mutex);
    555 
    556             // Process finished requests before cancellation so CURLMSG_DONE
    557             // never references an easy handle that cancellation already freed.
    558             int msgq = 0;
    559             while ((m = curl_multi_info_read(ccs->curlm, &msgq)) != NULL) {
    560                 if (m->msg == CURLMSG_DONE) {
    561                     CURL *e = m->easy_handle;
    562                     curl_multi_remove_handle(ccs->curlm, e);
    563                     handle_done(e, m->data.result);
    564                 }
    565             }
    566 
    567             // Cancel requests in queue
    568             pthread_mutex_lock(&ccs->mutex);
    569             list_for_each_safe(el, el1, &ccs->cancel_queue) {
    570                 struct CurlRequestState *crs = list_entry(el, struct CurlRequestState, link_cancel);
    571                 if (crs->added) {
    572                     curl_multi_remove_handle(ccs->curlm, crs->curl);
    573                 }
    574                 destroy_curl_request_state(crs);
    575             }
    576             pthread_mutex_unlock(&ccs->mutex);
    577         } while (m);
    578     }
    579     pthread_mutex_lock(&ccs->mutex);
    580     ccs->stopped = TRUE;
    581     pthread_mutex_unlock(&ccs->mutex);
    582     return NULL;
    583 }
    584 
    585 struct JSHttpClientImplementation *
    586 js_curl_http_client_create()
    587 {
    588     struct JSHttpClientImplementation *impl = NULL;
    589     struct CurlClientState *ccs = NULL;
    590     int res;
    591 
    592     if (0 != pthread_once(&curl_global_once, init_curl_global_state) ||
    593         CURLE_OK != curl_global_status) {
    594         return NULL;
    595     }
    596 
    597     ccs = calloc(1, sizeof *ccs);
    598     if (!ccs) {
    599         goto error;
    600     }
    601 
    602     pthread_mutex_init(&ccs->mutex, NULL);
    603     pthread_cond_init(&ccs->callback_done, NULL);
    604     ccs->curlsh = curl_share_init();
    605     if (!ccs->curlsh) {
    606       goto error;
    607     }
    608     ccs->curlm = curl_multi_init();
    609     if (!ccs->curlm) {
    610       goto error;
    611     }
    612     init_list_head(&ccs->request_list);
    613     init_list_head(&ccs->add_queue);
    614     init_list_head(&ccs->cancel_queue);
    615 
    616     curl_share_setopt(ccs->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
    617     curl_share_setopt(ccs->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
    618     curl_share_setopt(ccs->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
    619 
    620     impl = malloc(sizeof *impl);
    621     if (!impl) {
    622         goto error;
    623     }
    624     impl->req_create = &create_impl;
    625     impl->req_cancel = &destroy_impl;
    626     impl->cls = ccs;
    627 
    628     res = pthread_create(&ccs->thread, NULL, &curl_multi_thread_run, ccs);
    629     if (0 != res) {
    630         goto error;
    631     }
    632     ccs->started = TRUE;
    633 
    634     return impl;
    635 error:
    636     if (ccs) {
    637       if (ccs->curlsh) {
    638         curl_share_cleanup(ccs->curlsh);
    639       }
    640       if (ccs->curlm) {
    641         curl_multi_cleanup(ccs->curlm);
    642       }
    643       pthread_cond_destroy(&ccs->callback_done);
    644       pthread_mutex_destroy(&ccs->mutex);
    645       free(ccs);
    646     }
    647     if (impl) {
    648       free(impl);
    649     }
    650     return NULL;
    651 }
    652 
    653 static void
    654 destroy_client_state(struct CurlClientState *ccs)
    655 {
    656     struct list_head *el, *el1;
    657     if (!ccs) {
    658         return;
    659     }
    660     if (ccs->started == TRUE) {
    661         void *retval;
    662         int res;
    663 
    664         pthread_mutex_lock(&ccs->mutex);
    665         ccs->stopped = TRUE;
    666         pthread_mutex_unlock(&ccs->mutex);
    667         curl_multi_wakeup(ccs->curlm);
    668         res = pthread_join(ccs->thread, &retval);
    669         if (0 != res) {
    670             fprintf(stderr, "warning: could not join with curl thread\n");
    671         }
    672         ccs->started = FALSE;
    673     }
    674     pthread_mutex_lock(&ccs->mutex);
    675     list_for_each_safe(el, el1, &ccs->request_list) {
    676         struct CurlRequestState *crs = list_entry(el, struct CurlRequestState, link_req);
    677         if (crs->added) {
    678             curl_multi_remove_handle(ccs->curlm, crs->curl);
    679         }
    680         destroy_curl_request_state(crs);
    681     }
    682     pthread_mutex_unlock(&ccs->mutex);
    683     if (CURLM_OK != curl_multi_cleanup(ccs->curlm)) {
    684         fprintf(stderr, "warning: curl_multi_cleanup failed\n");
    685     }
    686     if (CURLSHE_OK != curl_share_cleanup(ccs->curlsh)) {
    687         fprintf(stderr, "warning: curl_share_cleanup failed\n");
    688     }
    689     pthread_cond_destroy(&ccs->callback_done);
    690     pthread_mutex_destroy(&ccs->mutex);
    691     free(ccs);
    692 }
    693 
    694 void
    695 js_curl_http_client_destroy(struct JSHttpClientImplementation *impl)
    696 {
    697     if (!impl) {
    698         return;
    699     }
    700     destroy_client_state(impl->cls);
    701     impl->cls = NULL;
    702     free(impl);
    703 }