diff --git a/lib/asyn-ares.c b/lib/asyn-ares.c index 6c7922914e..2fb83eeb10 100644 --- a/lib/asyn-ares.c +++ b/lib/asyn-ares.c @@ -203,32 +203,39 @@ CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl) return result; } -static void async_ares_destroy(struct Curl_easy *data); +static void async_ares_cleanup(struct Curl_easy *data); -/* - * For asyn-ares, this is the same as abort. - */ -void Curl_async_shutdown(struct Curl_easy *data) +void Curl_async_ares_shutdown(struct Curl_easy *data) { struct async_ares_ctx *ares = &data->state.async.ares; - if(ares->channel) { + if(ares->channel) ares_cancel(ares->channel); + async_ares_cleanup(data); +} + +void Curl_async_ares_destroy(struct Curl_easy *data) +{ + struct async_ares_ctx *ares = &data->state.async.ares; + Curl_async_ares_shutdown(data); + if(ares->channel) { + ares_destroy(ares->channel); ares->channel = NULL; } - async_ares_destroy(data); } /* - * async_ares_destroy() cleans up async resolver data. + * async_ares_cleanup() cleans up async resolver data. */ -static void async_ares_destroy(struct Curl_easy *data) +static void async_ares_cleanup(struct Curl_easy *data) { struct async_ares_ctx *ares = &data->state.async.ares; if(ares->temp_ai) { Curl_freeaddrinfo(ares->temp_ai); ares->temp_ai = NULL; } - Curl_safefree(data->state.async.hostname); +#ifdef USE_HTTPSRR + Curl_httpsrr_cleanup(&ares->hinfo); +#endif } /* @@ -323,7 +330,7 @@ CURLcode Curl_async_is_resolved(struct Curl_easy *data, *dns = data->state.async.dns; CURL_TRC_DNS(data, "is_resolved() result=%d, dns=%sfound", result, *dns ? "" : "not "); - async_ares_destroy(data); + async_ares_cleanup(data); } return result; } @@ -780,7 +787,7 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data, * default. */ if(!servers) { - Curl_async_shutdown(data); + Curl_async_destroy(data); result = async_ares_init_lazy(data); if(!result) { /* this now needs to restore the other options set to c-ares */ diff --git a/lib/asyn-base.c b/lib/asyn-base.c index 4b1063d45a..1c07436a2f 100644 --- a/lib/asyn-base.c +++ b/lib/asyn-base.c @@ -169,3 +169,37 @@ int Curl_ares_perform(ares_channel channel, #endif #endif /* CURLRES_ASYNCH */ + +#ifdef USE_CURL_ASYNC + +#include "doh.h" + +void Curl_async_shutdown(struct Curl_easy *data) +{ +#ifdef CURLRES_ARES + Curl_async_ares_shutdown(data); +#endif +#ifdef CURLRES_THREADED + Curl_async_thrdd_shutdown(data); +#endif +#ifndef CURL_DISABLE_DOH + Curl_doh_cleanup(data); +#endif + Curl_safefree(data->state.async.hostname); +} + +void Curl_async_destroy(struct Curl_easy *data) +{ +#ifdef CURLRES_ARES + Curl_async_ares_destroy(data); +#endif +#ifdef CURLRES_THREADED + Curl_async_thrdd_destroy(data); +#endif +#ifndef CURL_DISABLE_DOH + Curl_doh_cleanup(data); +#endif + Curl_safefree(data->state.async.hostname); +} + +#endif /* USE_CURL_ASYNC */ diff --git a/lib/asyn-thrdd.c b/lib/asyn-thrdd.c index a6c4df2b82..b11cf066d4 100644 --- a/lib/asyn-thrdd.c +++ b/lib/asyn-thrdd.c @@ -346,8 +346,6 @@ static void async_thrdd_destroy(struct Curl_easy *data) wakeup_close(sock_rd); #endif } - - Curl_safefree(data->state.async.hostname); } #ifdef USE_HTTPSRR_ARES @@ -496,7 +494,7 @@ static CURLcode asyn_thrdd_await(struct Curl_easy *data, * Until we gain a way to signal the resolver threads to stop early, we must * simply wait for them and ignore their results. */ -void Curl_async_shutdown(struct Curl_easy *data) +void Curl_async_thrdd_shutdown(struct Curl_easy *data) { struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; @@ -510,6 +508,11 @@ void Curl_async_shutdown(struct Curl_easy *data) async_thrdd_destroy(data); } +void Curl_async_thrdd_destroy(struct Curl_easy *data) +{ + Curl_async_thrdd_shutdown(data); +} + /* * Curl_async_await() * diff --git a/lib/asyn.h b/lib/asyn.h index 7bc791f3c4..39de04320d 100644 --- a/lib/asyn.h +++ b/lib/asyn.h @@ -69,13 +69,6 @@ void Curl_async_global_cleanup(void); */ CURLcode Curl_async_get_impl(struct Curl_easy *easy, void **impl); -/* - * Curl_async_shutdown(). - * - * This frees the resources of any async resolve operation. - */ -void Curl_async_shutdown(struct Curl_easy *data); - /* Curl_async_getsock() * * This function is called from the Curl_multi_getsock() function. 'sock' is a @@ -157,6 +150,9 @@ struct async_ares_ctx { #endif }; +void Curl_async_ares_shutdown(struct Curl_easy *data); +void Curl_async_ares_destroy(struct Curl_easy *data); + /* * Function provided by the resolver backend to set DNS servers to use. */ @@ -227,6 +223,9 @@ struct async_thrdd_ctx { #endif }; +void Curl_async_thrdd_shutdown(struct Curl_easy *data); +void Curl_async_thrdd_destroy(struct Curl_easy *data); + #endif /* CURLRES_THREADED */ #ifndef CURL_DISABLE_DOH @@ -237,7 +236,6 @@ struct doh_probes; /* convert these functions if an asynch resolver is not used */ #define Curl_async_get_impl(x,y) (*(y) = NULL, CURLE_OK) -#define Curl_async_shutdown(x) Curl_nop_stmt #define Curl_async_is_resolved(x,y) CURLE_COULDNT_RESOLVE_HOST #define Curl_async_await(x,y) CURLE_COULDNT_RESOLVE_HOST #define Curl_async_global_init() CURLE_OK @@ -247,7 +245,9 @@ struct doh_probes; #if defined(CURLRES_ASYNCH) || !defined(CURL_DISABLE_DOH) #define USE_CURL_ASYNC +#endif +#ifdef USE_CURL_ASYNC struct Curl_async { #ifdef CURLRES_ARES /* */ struct async_ares_ctx ares; @@ -264,7 +264,23 @@ struct Curl_async { BIT(done); }; -#endif +/* + * Curl_async_shutdown(). + * + * This shuts down all ongoing operations. + */ +void Curl_async_shutdown(struct Curl_easy *data); + +/* + * Curl_async_destroy(). + * + * This frees the resources of any async resolve. + */ +void Curl_async_destroy(struct Curl_easy *data); +#else /* !USE_CURL_ASYNC */ +#define Curl_async_shutdown(x) Curl_nop_stmt +#endif /* USE_CURL_ASYNC */ + /********** end of generic resolver interface functions *****************/ #endif /* HEADER_CURL_ASYN_H */ diff --git a/lib/doh.c b/lib/doh.c index 5b70fffc24..65506ea323 100644 --- a/lib/doh.c +++ b/lib/doh.c @@ -175,12 +175,15 @@ UNITTEST DOHcode doh_req_encode(const char *host, } static size_t -doh_write_cb(char *contents, size_t size, size_t nmemb, void *userp) +doh_probe_write_cb(char *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; - struct dynbuf *mem = (struct dynbuf *)userp; + struct Curl_easy *data = userp; + struct doh_request *doh_req = Curl_meta_get(data, CURL_EZM_DOH_PROBE); + if(!doh_req) + return CURL_WRITEFUNC_ERROR; - if(Curl_dyn_addn(mem, contents, realsize)) + if(Curl_dyn_addn(&doh_req->resp_body, contents, realsize)) return 0; return realsize; @@ -210,22 +213,45 @@ static void doh_print_buf(struct Curl_easy *data, } #endif -/* called from multi.c when this DoH transfer is complete */ -static int doh_done(struct Curl_easy *doh, CURLcode result) +/* called from multi when a sub transfer, e.g. doh probe, is done. + * This looks up the the probe response at its meta CURL_EZM_DOH_PROBE + * and copies the response body over to the struct at the master's + * meta at CURL_EZM_DOH_MASTER. */ +static void doh_probe_done(struct Curl_easy *data, + struct Curl_easy *doh, CURLcode result) { - struct Curl_easy *data; /* the transfer that asked for the DoH probe */ + struct doh_probes *dohp = data->state.async.doh; + DEBUGASSERT(dohp); + if(dohp) { + struct doh_request *doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE); + int i; + + for(i = 0; i < DOH_SLOT_COUNT; ++i) { + if(dohp->probe_resp[i].probe_mid == doh->mid) + break; + } + if(i >= DOH_SLOT_COUNT) { + failf(data, "unknown sub request done"); + return; + } - data = Curl_multi_get_handle(doh->multi, doh->set.dohfor_mid); - if(!data) { - DEBUGF(infof(doh, "doh_done: xfer for mid=%" FMT_OFF_T - " not found", doh->set.dohfor_mid)); - DEBUGASSERT(0); - } - else { - struct doh_probes *dohp = data->state.async.doh; - /* one of the DoH request done for the 'data' transfer is now complete! */ dohp->pending--; infof(doh, "a DoH request is completed, %u to go", dohp->pending); + dohp->probe_resp[i].result = result; + /* We expect either the meta data still to exist or the sub request + * to have already failed. */ + DEBUGASSERT(doh_req || result); + if(doh_req) { + if(!result) { + dohp->probe_resp[i].dnstype = doh_req->dnstype; + result = Curl_dyn_addn(&dohp->probe_resp[i].body, + Curl_dyn_ptr(&doh_req->resp_body), + Curl_dyn_len(&doh_req->resp_body)); + Curl_dyn_free(&doh_req->resp_body); + } + Curl_meta_clear(doh, CURL_EZM_DOH_PROBE); + } + if(result) infof(doh, "DoH request %s", curl_easy_strerror(result)); @@ -234,7 +260,18 @@ static int doh_done(struct Curl_easy *doh, CURLcode result) Curl_expire(data, 0, EXPIRE_RUN_NOW); } } - return 0; +} + +static void doh_probe_dtor(void *key, size_t klen, void *e) +{ + (void)key; + (void)klen; + if(e) { + struct doh_request *doh_req = e; + curl_slist_free_all(doh_req->req_hds); + Curl_dyn_free(&doh_req->resp_body); + free(e); + } } #define ERROR_CHECK_SETOPT(x,y) \ @@ -246,30 +283,48 @@ static int doh_done(struct Curl_easy *doh, CURLcode result) goto error; \ } while(0) -static CURLcode doh_run_probe(struct Curl_easy *data, - struct doh_probe *p, DNStype dnstype, +static CURLcode doh_probe_run(struct Curl_easy *data, + DNStype dnstype, const char *host, const char *url, CURLM *multi, - struct curl_slist *headers) + curl_off_t *pmid) { struct Curl_easy *doh = NULL; CURLcode result = CURLE_OK; timediff_t timeout_ms; - DOHcode d = doh_req_encode(host, dnstype, p->req_body, sizeof(p->req_body), - &p->req_body_len); + struct doh_request *doh_req; + DOHcode d; + + *pmid = -1; + + doh_req = calloc(1, sizeof(*doh_req)); + if(!doh_req) + return CURLE_OUT_OF_MEMORY; + doh_req->dnstype = dnstype; + Curl_dyn_init(&doh_req->resp_body, DYN_DOH_RESPONSE); + + d = doh_req_encode(host, dnstype, doh_req->req_body, + sizeof(doh_req->req_body), + &doh_req->req_body_len); if(d) { failf(data, "Failed to encode DoH packet [%d]", d); - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto error; } - p->dnstype = dnstype; - Curl_dyn_init(&p->resp_body, DYN_DOH_RESPONSE); - timeout_ms = Curl_timeleft(data, NULL, TRUE); if(timeout_ms <= 0) { result = CURLE_OPERATION_TIMEDOUT; goto error; } + + doh_req->req_hds = + curl_slist_append(NULL, "Content-Type: application/dns-message"); + if(!doh_req->req_hds) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + /* Curl_open() is the internal version of curl_easy_init() */ result = Curl_open(&doh); if(result) @@ -277,17 +332,16 @@ static CURLcode doh_run_probe(struct Curl_easy *data, /* pass in the struct pointer via a local variable to please coverity and the gcc typecheck helpers */ - doh->state.internal = TRUE; #ifndef CURL_DISABLE_VERBOSE_STRINGS doh->state.feat = &Curl_trc_feat_dns; #endif ERROR_CHECK_SETOPT(CURLOPT_URL, url); ERROR_CHECK_SETOPT(CURLOPT_DEFAULT_PROTOCOL, "https"); - ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_write_cb); - ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, &p->resp_body); - ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, p->req_body); - ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)p->req_body_len); - ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, headers); + ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_probe_write_cb); + ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, doh); + ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, doh_req->req_body); + ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)doh_req->req_body_len); + ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, doh_req->req_hds); #ifdef USE_HTTP2 ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); ERROR_CHECK_SETOPT(CURLOPT_PIPEWAIT, 1L); @@ -374,8 +428,14 @@ static CURLcode doh_run_probe(struct Curl_easy *data, (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, mask); } - doh->set.fmultidone = doh_done; - doh->set.dohfor_mid = data->mid; /* for which transfer this is done */ + doh->state.internal = TRUE; + doh->master_mid = data->mid; /* master transfer of this one */ + + if(Curl_meta_set(doh, CURL_EZM_DOH_PROBE, doh_req, doh_probe_dtor)) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + doh_req = NULL; /* DoH handles must not inherit private_data. The handles may be passed to the user via callbacks and the user will be able to identify them as @@ -386,12 +446,13 @@ static CURLcode doh_run_probe(struct Curl_easy *data, if(curl_multi_add_handle(multi, doh)) goto error; - p->easy_mid = doh->mid; + *pmid = doh->mid; return CURLE_OK; error: Curl_close(&doh); - p->easy_mid = -1; + if(doh_req) + doh_probe_dtor(NULL, 0, doh_req); return result; } @@ -407,14 +468,14 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, int *waitp) { CURLcode result = CURLE_OK; - struct doh_probes *dohp; + struct doh_probes *dohp = NULL; struct connectdata *conn = data->conn; size_t i; - *waitp = FALSE; - - DEBUGASSERT(!data->state.async.doh); DEBUGASSERT(conn); + DEBUGASSERT(!data->state.async.doh); + if(data->state.async.doh) + Curl_doh_cleanup(data); data->state.async.done = FALSE; data->state.async.port = port; @@ -424,28 +485,27 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, return NULL; /* start clean, consider allocating this struct on demand */ - dohp = data->state.async.doh = calloc(1, sizeof(struct doh_probes)); + data->state.async.doh = dohp = calloc(1, sizeof(struct doh_probes)); if(!dohp) return NULL; for(i = 0; i < DOH_SLOT_COUNT; ++i) { - dohp->probe[i].easy_mid = -1; + dohp->probe_resp[i].probe_mid = -1; + Curl_dyn_init(&dohp->probe_resp[i].body, DYN_DOH_RESPONSE); } conn->bits.doh = TRUE; dohp->host = data->state.async.hostname; dohp->port = data->state.async.port; - dohp->req_hds = - curl_slist_append(NULL, - "Content-Type: application/dns-message"); - if(!dohp->req_hds) - goto error; + /* We are making sub easy handles and want to be called back when + * one is done. */ + data->sub_xfer_done = doh_probe_done; /* create IPv4 DoH request */ - (void)ip_version; /* WHY not select on this for ipv4? */ - result = doh_run_probe(data, &dohp->probe[DOH_SLOT_IPV4], - DNS_TYPE_A, hostname, data->set.str[STRING_DOH], - data->multi, dohp->req_hds); + result = doh_probe_run(data, DNS_TYPE_A, + hostname, data->set.str[STRING_DOH], + data->multi, + &dohp->probe_resp[DOH_SLOT_IPV4].probe_mid); if(result) goto error; dohp->pending++; @@ -453,9 +513,10 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, #ifdef USE_IPV6 if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { /* create IPv6 DoH request */ - result = doh_run_probe(data, &dohp->probe[DOH_SLOT_IPV6], - DNS_TYPE_AAAA, hostname, data->set.str[STRING_DOH], - data->multi, dohp->req_hds); + result = doh_probe_run(data, DNS_TYPE_AAAA, + hostname, data->set.str[STRING_DOH], + data->multi, + &dohp->probe_resp[DOH_SLOT_IPV6].probe_mid); if(result) goto error; dohp->pending++; @@ -471,10 +532,10 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, if(!qname) goto error; } - result = doh_run_probe(data, &dohp->probe[DOH_SLOT_HTTPS_RR], - DNS_TYPE_HTTPS, + result = doh_probe_run(data, DNS_TYPE_HTTPS, qname ? qname : hostname, data->set.str[STRING_DOH], - data->multi, dohp->req_hds); + data->multi, + &dohp->probe_resp[DOH_SLOT_HTTPS_RR].probe_mid); free(qname); if(result) goto error; @@ -1177,8 +1238,8 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, if(!dohp) return CURLE_OUT_OF_MEMORY; - if(dohp->probe[DOH_SLOT_IPV4].easy_mid < 0 && - dohp->probe[DOH_SLOT_IPV6].easy_mid < 0) { + if(dohp->probe_resp[DOH_SLOT_IPV4].probe_mid < 0 && + dohp->probe_resp[DOH_SLOT_IPV6].probe_mid < 0) { failf(data, "Could not DoH-resolve: %s", dohp->host); return CONN_IS_PROXIED(data->conn) ? CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST; @@ -1197,13 +1258,12 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, /* parse the responses, create the struct and return it! */ de_init(&de); for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { - struct doh_probe *p = &dohp->probe[slot]; + struct doh_response *p = &dohp->probe_resp[slot]; if(!p->dnstype) continue; - rc[slot] = doh_resp_decode(Curl_dyn_uptr(&p->resp_body), - Curl_dyn_len(&p->resp_body), + rc[slot] = doh_resp_decode(Curl_dyn_uptr(&p->body), + Curl_dyn_len(&p->body), p->dnstype, &de); - Curl_dyn_free(&p->resp_body); #ifndef CURL_DISABLE_VERBOSE_STRINGS if(rc[slot]) { infof(data, "DoH: %s type %s for %s", doh_strerror(rc[slot]), @@ -1277,37 +1337,38 @@ void Curl_doh_close(struct Curl_easy *data) curl_off_t mid; size_t slot; for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { - mid = doh->probe[slot].easy_mid; + mid = doh->probe_resp[slot].probe_mid; if(mid < 0) continue; - doh->probe[slot].easy_mid = -1; - /* should have been called before data is removed from multi handle */ + doh->probe_resp[slot].probe_mid = -1; DEBUGASSERT(data->multi); probe_data = data->multi ? Curl_multi_get_handle(data->multi, mid) : NULL; if(!probe_data) { DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%" FMT_OFF_T " not found!", - doh->probe[slot].easy_mid)); + doh->probe_resp[slot].probe_mid)); continue; } /* data->multi might already be reset at this time */ curl_multi_remove_handle(data->multi, probe_data); Curl_close(&probe_data); } + data->sub_xfer_done = NULL; } } void Curl_doh_cleanup(struct Curl_easy *data) { - struct doh_probes *doh = data->state.async.doh; - if(doh) { + struct doh_probes *dohp = data->state.async.doh; + if(dohp) { + int i; Curl_doh_close(data); - curl_slist_free_all(doh->req_hds); - data->state.async.doh->req_hds = NULL; + for(i = 0; i < DOH_SLOT_COUNT; ++i) { + Curl_dyn_free(&dohp->probe_resp[i].body); + } Curl_safefree(data->state.async.doh); } - Curl_safefree(data->state.async.hostname); } #endif /* CURL_DISABLE_DOH */ diff --git a/lib/doh.h b/lib/doh.h index de709cf06d..ddabac8863 100644 --- a/lib/doh.h +++ b/lib/doh.h @@ -59,15 +59,6 @@ typedef enum { DNS_TYPE_HTTPS = 65 } DNStype; -/* one of these for each DoH request */ -struct doh_probe { - curl_off_t easy_mid; /* multi id of easy handle doing the lookup */ - DNStype dnstype; - unsigned char req_body[512]; - size_t req_body_len; - struct dynbuf resp_body; -}; - enum doh_slot_num { /* Explicit values for first two symbols so as to match hard-coded * constants in existing code @@ -89,9 +80,29 @@ enum doh_slot_num { DOH_SLOT_COUNT }; -struct doh_probes { +#define CURL_EZM_DOH_PROBE "ezm:doh-p" + +/* each DoH probe request has this + * as easy meta for CURL_EZM_DOH_PROBE */ +struct doh_request { + DNStype dnstype; + unsigned char req_body[512]; + size_t req_body_len; struct curl_slist *req_hds; - struct doh_probe probe[DOH_SLOT_COUNT]; + struct dynbuf resp_body; +}; + +struct doh_response { + curl_off_t probe_mid; + struct dynbuf body; + DNStype dnstype; + CURLcode result; +}; + +/* each transfer firing off DoH requests has this + * as easy meta for CURL_EZM_DOH_MASTER */ +struct doh_probes { + struct doh_response probe_resp[DOH_SLOT_COUNT]; unsigned int pending; /* still outstanding probes */ int port; const char *host; diff --git a/lib/easy.c b/lib/easy.c index 5376492578..0481454465 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -938,6 +938,15 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) return result; } +static void dupeasy_meta_freeentry(void *p) +{ + (void)p; + /* Will always be FALSE. Cannot use a 0 assert here since compilers + * are not in agreement if they then want a NORETURN attribute or + * not. *sigh* */ + DEBUGASSERT(p == NULL); +} + /* * curl_easy_duphandle() is an external interface to allow duplication of a * given input easy handle. The returned handle will be a new working handle @@ -957,6 +966,8 @@ CURL *curl_easy_duphandle(CURL *d) */ outcurl->set.buffer_size = data->set.buffer_size; + Curl_hash_init(&outcurl->meta_hash, 23, + Curl_hash_str, Curl_str_key_compare, dupeasy_meta_freeentry); Curl_dyn_init(&outcurl->state.headerb, CURL_MAX_HTTP_HEADER); Curl_netrc_init(&outcurl->state.netrc); @@ -965,6 +976,7 @@ CURL *curl_easy_duphandle(CURL *d) outcurl->state.recent_conn_id = -1; outcurl->id = -1; outcurl->mid = -1; + outcurl->master_mid = -1; #ifndef CURL_DISABLE_HTTP Curl_llist_init(&outcurl->state.httphdrs, NULL); @@ -1090,7 +1102,12 @@ void curl_easy_reset(CURL *d) { struct Curl_easy *data = d; Curl_req_hard_reset(&data->req, data); + Curl_hash_clean(&data->meta_hash); + /* clear all meta data */ + Curl_meta_reset(data); + /* clear any async resolve data */ + Curl_async_shutdown(data); /* zero out UserDefined data: */ Curl_freeset(data); memset(&data->set, 0, sizeof(struct UserDefined)); @@ -1113,6 +1130,7 @@ void curl_easy_reset(CURL *d) #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH) Curl_http_auth_cleanup_digest(data); #endif + data->master_mid = -1; } /* @@ -1390,3 +1408,28 @@ CURLcode curl_easy_ssls_export(CURL *d, return CURLE_NOT_BUILT_IN; #endif } + +CURLcode Curl_meta_set(struct Curl_easy *data, const char *key, + void *meta_data, Curl_meta_dtor *meta_dtor) +{ + if(!Curl_hash_add2(&data->meta_hash, CURL_UNCONST(key), strlen(key) + 1, + meta_data, meta_dtor)) { + meta_dtor(CURL_UNCONST(key), strlen(key) + 1, meta_data); + } + return CURLE_OK; +} + +void Curl_meta_clear(struct Curl_easy *data, const char *key) +{ + Curl_hash_delete(&data->meta_hash, CURL_UNCONST(key), strlen(key) + 1); +} + +void *Curl_meta_get(struct Curl_easy *data, const char *key) +{ + return Curl_hash_pick(&data->meta_hash, CURL_UNCONST(key), strlen(key) + 1); +} + +void Curl_meta_reset(struct Curl_easy *data) +{ + Curl_hash_clean(&data->meta_hash); +} diff --git a/lib/hostip.c b/lib/hostip.c index eb552af875..43412abb50 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -843,10 +843,12 @@ CURLcode Curl_resolv(struct Curl_easy *data, /* No luck, we need to resolve hostname. Notify user callback. */ if(data->set.resolver_start) { - void *resolver; + void *resolver = NULL; int st; +#ifdef CURLRES_ASYNCH if(Curl_async_get_impl(data, &resolver)) goto error; +#endif Curl_set_in_callback(data, TRUE); st = data->set.resolver_start(resolver, NULL, data->set.resolver_start_client); @@ -924,6 +926,7 @@ error: if(dns) Curl_resolv_unlink(data, &dns); *entry = NULL; + Curl_async_shutdown(data); return CURLE_COULDNT_RESOLVE_HOST; } @@ -1487,11 +1490,11 @@ CURLcode Curl_resolv_check(struct Curl_easy *data, data->state.async.ip_version); if(*dns) { /* Tell a possibly async resolver we no longer need the results. */ + infof(data, "Hostname '%s' was found in DNS cache", + data->state.async.hostname); Curl_async_shutdown(data); data->state.async.dns = *dns; data->state.async.done = TRUE; - infof(data, "Hostname '%s' was found in DNS cache", - data->state.async.hostname); return CURLE_OK; } diff --git a/lib/multi.c b/lib/multi.c index c3dc3fb5e2..6afcf7a895 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -727,6 +727,7 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *d) data->multi = NULL; /* clear the association to this multi handle */ data->mid = -1; + data->master_mid = -1; /* NOTE NOTE NOTE We do not touch the easy handle here! */ @@ -2507,9 +2508,24 @@ statemachine_end: } if(MSTATE_COMPLETED == data->mstate) { - if(data->set.fmultidone) { - /* signal via callback instead */ - data->set.fmultidone(data, result); + if(data->master_mid >= 0) { + /* A sub transfer, not for msgsent to application */ + struct Curl_easy *mdata; + + CURL_TRC_M(data, "sub xfer done for master %" FMT_OFF_T, + data->master_mid); + mdata = Curl_multi_get_handle(multi, data->master_mid); + if(mdata) { + if(mdata->sub_xfer_done) + mdata->sub_xfer_done(mdata, data, result); + else + CURL_TRC_M(data, "master easy %" FMT_OFF_T + " without sub_xfer_done.", data->master_mid); + } + else { + CURL_TRC_M(data, "master easy %" FMT_OFF_T " already gone.", + data->master_mid); + } } else { /* now fill in the Curl_message with this info */ diff --git a/lib/request.c b/lib/request.c index 513d4f2c4b..3b8f212123 100644 --- a/lib/request.c +++ b/lib/request.c @@ -178,10 +178,6 @@ void Curl_req_free(struct SingleRequest *req, struct Curl_easy *data) if(req->sendbuf_init) Curl_bufq_free(&req->sendbuf); Curl_client_cleanup(data); - -#ifndef CURL_DISABLE_DOH - Curl_doh_cleanup(data); -#endif } static CURLcode xfer_send(struct Curl_easy *data, diff --git a/lib/url.c b/lib/url.c index 856d71366e..efc79ccd11 100644 --- a/lib/url.c +++ b/lib/url.c @@ -290,7 +290,7 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_safefree(data->info.contenttype); Curl_safefree(data->info.wouldredirect); - Curl_async_shutdown(data); + Curl_async_destroy(data); data_priority_cleanup(data); @@ -301,6 +301,7 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); } + Curl_hash_destroy(&data->meta_hash); #ifndef CURL_DISABLE_PROXY Curl_safefree(data->state.aptr.proxyuserpwd); #endif @@ -364,10 +365,6 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) set->postfieldsize = -1; /* unknown size */ set->maxredirs = 30; /* sensible default */ -#ifndef CURL_DISABLE_DOH - set->dohfor_mid = -1; -#endif - set->method = HTTPREQ_GET; /* Default HTTP request */ #ifndef CURL_DISABLE_RTSP set->rtspreq = RTSPREQ_OPTIONS; /* Default RTSP request */ @@ -486,6 +483,17 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) return result; } +/* easy->meta_hash destructor. Should never be called as elements + * MUST be added with their own destructor */ +static void easy_meta_freeentry(void *p) +{ + (void)p; + /* Will always be FALSE. Cannot use a 0 assert here since compilers + * are not in agreement if they then want a NORETURN attribute or + * not. *sigh* */ + DEBUGASSERT(p == NULL); +} + /** * Curl_open() * @@ -509,6 +517,8 @@ CURLcode Curl_open(struct Curl_easy **curl) data->magic = CURLEASY_MAGIC_NUMBER; + Curl_hash_init(&data->meta_hash, 23, + Curl_hash_str, Curl_str_key_compare, easy_meta_freeentry); Curl_dyn_init(&data->state.headerb, CURL_MAX_HTTP_HEADER); Curl_req_init(&data->req); Curl_initinfo(data); @@ -527,9 +537,7 @@ CURLcode Curl_open(struct Curl_easy **curl) /* and not assigned an id yet */ data->id = -1; data->mid = -1; -#ifndef CURL_DISABLE_DOH - data->set.dohfor_mid = -1; -#endif + data->master_mid = -1; data->progress.flags |= PGRS_HIDE; data->state.current_speed = -1; /* init to negative == impossible */ @@ -539,6 +547,7 @@ out: Curl_dyn_free(&data->state.headerb); Curl_freeset(data); Curl_req_free(&data->req, data); + Curl_hash_destroy(&data->meta_hash); free(data); data = NULL; } @@ -3612,12 +3621,10 @@ static CURLcode create_conn(struct Curl_easy *data, connections_available = FALSE; break; case CPOOL_LIMIT_TOTAL: -#ifndef CURL_DISABLE_DOH - if(data->set.dohfor_mid >= 0) - infof(data, "Allowing DoH to override max connection limit"); - else -#endif - { + if(data->master_mid >= 0) + CURL_TRC_M(data, "Allowing sub-requests (like DoH) to override " + "max connection limit"); + else { infof(data, "No connections available, total of %ld reached.", data->multi->max_total_connections); connections_available = FALSE; diff --git a/lib/url.h b/lib/url.h index f60460671e..aa687228ab 100644 --- a/lib/url.h +++ b/lib/url.h @@ -44,6 +44,20 @@ CURLcode Curl_parse_login_details(const char *login, const size_t len, char **userptr, char **passwdptr, char **optionsptr); +/* Attach/Clear/Get meta data for an easy handle. Needs to provide + * a destructor, will be automatically called when the easy handle + * is reset or closed. */ +typedef void Curl_meta_dtor(void *key, size_t key_len, void *meta_data); + +/* Set the transfer meta data for the key. Any existing entry for that + * key will be destroyed. + * Takes ownership of `meta_data` and destroys it when the call fails. */ +CURLcode Curl_meta_set(struct Curl_easy *data, const char *key, + void *meta_data, Curl_meta_dtor *meta_dtor); +void Curl_meta_clear(struct Curl_easy *data, const char *key); +void *Curl_meta_get(struct Curl_easy *data, const char *key); +void Curl_meta_reset(struct Curl_easy *data); + /* Get protocol handler for a URI scheme * @param scheme URI scheme, case-insensitive * @return NULL of handler not found diff --git a/lib/urldata.h b/lib/urldata.h index ebcfeb2a7f..58cdb6023b 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1497,10 +1497,6 @@ enum dupblob { BLOB_LAST }; -/* callback that gets called when this easy handle is completed within a multi - handle. Only used for internally created transfers, like for example - DoH. */ -typedef int (*multidone_func)(struct Curl_easy *easy, CURLcode result); struct UserDefined { FILE *err; /* the stderr user data goes here */ @@ -1656,10 +1652,6 @@ struct UserDefined { before resolver start */ void *resolver_start_client; /* pointer to pass to resolver start callback */ long upkeep_interval_ms; /* Time between calls for connection upkeep. */ - multidone_func fmultidone; -#ifndef CURL_DISABLE_DOH - curl_off_t dohfor_mid; /* this is a DoH request for that transfer */ -#endif CURLU *uh; /* URL handle for the current parsed URL */ #ifndef CURL_DISABLE_HTTP void *trailer_data; /* pointer to pass to trailer data callback */ @@ -1818,6 +1810,12 @@ struct UserDefined { #define IS_MIME_POST(a) FALSE #endif +/* callback that gets called when a sub easy (data->master_mid set) is + DONE. Called on the master easy. */ +typedef void multi_sub_xfer_done_cb(struct Curl_easy *master_easy, + struct Curl_easy *sub_easy, + CURLcode result); + /* * The 'connectdata' struct MUST have all the connection oriented stuff as we * may have several simultaneous connections and connection structs in memory. @@ -1843,6 +1841,8 @@ struct Curl_easy { * libcurl application or implicitly during `curl_easy_perform()`, * a unique identifier inside this one multi instance. */ curl_off_t mid; + curl_off_t master_mid; /* if set, this transfer belongs to a master */ + multi_sub_xfer_done_cb *sub_xfer_done; struct connectdata *conn; struct Curl_llist_node multi_queue; /* for multihandle list management */ @@ -1860,6 +1860,13 @@ struct Curl_easy { struct to which this "belongs" when used by the easy interface */ struct Curl_share *share; /* Share, handles global variable mutexing */ + + /* `meta_hash` is a general key-value store for implementations + * with the lifetime of the easy handle. + * Elements need to be added with their own destructor to be invoked when + * the easy handle is cleaned up (see Curl_hash_add2()).*/ + struct Curl_hash meta_hash; + #ifdef USE_LIBPSL struct PslCache *psl; /* The associated PSL cache. */ #endif diff --git a/scripts/singleuse.pl b/scripts/singleuse.pl index 7732387226..13a9ff54e1 100755 --- a/scripts/singleuse.pl +++ b/scripts/singleuse.pl @@ -47,6 +47,7 @@ my %wl = ( 'Curl_creader_def_close' => 'internal api', 'Curl_creader_def_read' => 'internal api', 'Curl_creader_def_total_length' => 'internal api', + 'Curl_meta_reset' => 'internal api', 'Curl_trc_dns' => 'internal api', );