mirror of
https://github.com/curl/curl.git
synced 2026-04-11 12:01:42 +08:00
Connection filters can now carry the flag CF_TYPE_SETUP, indicating that they are only needed during connection setup, e.g. connect. Once the connection is fully established, those filter are removed again. This frees resources and also makes the filter (call) chains shorter. Closes #21269
613 lines
18 KiB
C
613 lines
18 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* Project ___| | | | _ \| |
|
|
* / __| | | | |_) | |
|
|
* | (__| |_| | _ <| |___
|
|
* \___|\___/|_| \_\_____|
|
|
*
|
|
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
|
*
|
|
* This software is licensed as described in the file COPYING, which
|
|
* you should have received as part of this distribution. The terms
|
|
* are also available at https://curl.se/docs/copyright.html.
|
|
*
|
|
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
|
* copies of the Software, and permit persons to whom the Software is
|
|
* furnished to do so, under the terms of the COPYING file.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
* SPDX-License-Identifier: curl
|
|
*
|
|
***************************************************************************/
|
|
#include "curl_setup.h"
|
|
|
|
#include "urldata.h"
|
|
#include "curl_addrinfo.h"
|
|
#include "cfilters.h"
|
|
#include "connect.h"
|
|
#include "dnscache.h"
|
|
#include "curl_trc.h"
|
|
#include "progress.h"
|
|
#include "url.h"
|
|
#include "cf-dns.h"
|
|
|
|
|
|
struct cf_dns_ctx {
|
|
struct Curl_dns_entry *dns;
|
|
CURLcode resolv_result;
|
|
uint32_t resolv_id;
|
|
uint16_t port;
|
|
uint8_t dns_queries;
|
|
uint8_t transport;
|
|
BIT(started);
|
|
BIT(announced);
|
|
BIT(abstract_unix_socket);
|
|
char hostname[1];
|
|
};
|
|
|
|
static struct cf_dns_ctx *
|
|
cf_dns_ctx_create(struct Curl_easy *data, uint8_t dns_queries,
|
|
const char *hostname, uint16_t port, uint8_t transport,
|
|
bool abstract_unix_socket,
|
|
struct Curl_dns_entry *dns)
|
|
{
|
|
struct cf_dns_ctx *ctx;
|
|
size_t hlen = strlen(hostname);
|
|
|
|
ctx = curlx_calloc(1, sizeof(*ctx) + hlen);
|
|
if(!ctx)
|
|
return NULL;
|
|
|
|
ctx->port = port;
|
|
ctx->dns_queries = dns_queries;
|
|
ctx->transport = transport;
|
|
ctx->abstract_unix_socket = abstract_unix_socket;
|
|
ctx->dns = Curl_dns_entry_link(data, dns);
|
|
ctx->started = !!ctx->dns;
|
|
if(hlen)
|
|
memcpy(ctx->hostname, hostname, hlen);
|
|
|
|
return ctx;
|
|
}
|
|
|
|
static void cf_dns_ctx_destroy(struct Curl_easy *data,
|
|
struct cf_dns_ctx *ctx)
|
|
{
|
|
if(ctx) {
|
|
Curl_dns_entry_unlink(data, &ctx->dns);
|
|
curlx_free(ctx);
|
|
}
|
|
}
|
|
|
|
#ifdef CURLVERBOSE
|
|
static void cf_dns_report_addr(struct Curl_easy *data,
|
|
struct dynbuf *tmp,
|
|
const char *label,
|
|
int ai_family,
|
|
const struct Curl_addrinfo *ai)
|
|
{
|
|
char buf[MAX_IPADR_LEN];
|
|
const char *sep = "";
|
|
CURLcode result;
|
|
|
|
curlx_dyn_reset(tmp);
|
|
for(; ai; ai = ai->ai_next) {
|
|
if(ai->ai_family == ai_family) {
|
|
Curl_printable_address(ai, buf, sizeof(buf));
|
|
result = curlx_dyn_addf(tmp, "%s%s", sep, buf);
|
|
if(result) {
|
|
infof(data, "too many IP, cannot show");
|
|
return;
|
|
}
|
|
sep = ", ";
|
|
}
|
|
}
|
|
|
|
infof(data, "%s%s", label,
|
|
(curlx_dyn_len(tmp) ? curlx_dyn_ptr(tmp) : "(none)"));
|
|
}
|
|
|
|
static void cf_dns_report(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
struct Curl_dns_entry *dns)
|
|
{
|
|
struct cf_dns_ctx *ctx = cf->ctx;
|
|
struct dynbuf tmp;
|
|
|
|
if(!Curl_trc_is_verbose(data) ||
|
|
/* ignore no name or numerical IP addresses */
|
|
!dns->hostname[0] || Curl_host_is_ipnum(dns->hostname))
|
|
return;
|
|
|
|
switch(ctx->transport) {
|
|
case TRNSPRT_UNIX:
|
|
#ifdef USE_UNIX_SOCKETS
|
|
CURL_TRC_CF(data, cf, "resolved unix domain %s",
|
|
Curl_conn_get_unix_path(data->conn));
|
|
#else
|
|
DEBUGASSERT(0);
|
|
#endif
|
|
break;
|
|
default:
|
|
curlx_dyn_init(&tmp, 1024);
|
|
infof(data, "Host %s:%d was resolved.", dns->hostname, dns->port);
|
|
#ifdef CURLRES_IPV6
|
|
cf_dns_report_addr(data, &tmp, "IPv6: ", AF_INET6, dns->addr);
|
|
#endif
|
|
cf_dns_report_addr(data, &tmp, "IPv4: ", AF_INET, dns->addr);
|
|
curlx_dyn_free(&tmp);
|
|
break;
|
|
}
|
|
}
|
|
#else
|
|
#define cf_dns_report(x, y, z) Curl_nop_stmt
|
|
#endif
|
|
|
|
/*************************************************************
|
|
* Resolve the address of the server or proxy
|
|
*************************************************************/
|
|
static CURLcode cf_dns_start(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
struct Curl_dns_entry **pdns)
|
|
{
|
|
struct cf_dns_ctx *ctx = cf->ctx;
|
|
timediff_t timeout_ms = Curl_timeleft_ms(data);
|
|
CURLcode result;
|
|
|
|
*pdns = NULL;
|
|
|
|
#ifdef USE_UNIX_SOCKETS
|
|
if(ctx->transport == TRNSPRT_UNIX) {
|
|
CURL_TRC_CF(data, cf, "resolve unix socket %s", ctx->hostname);
|
|
return Curl_resolv_unix(data, ctx->hostname,
|
|
(bool)cf->conn->bits.abstract_unix_socket, pdns);
|
|
}
|
|
#endif
|
|
|
|
/* Resolve target host right on */
|
|
CURL_TRC_CF(data, cf, "resolve host %s:%u", ctx->hostname, ctx->port);
|
|
result = Curl_resolv(data, ctx->dns_queries,
|
|
ctx->hostname, ctx->port, ctx->transport,
|
|
timeout_ms, &ctx->resolv_id, pdns);
|
|
DEBUGASSERT(!result || !*pdns);
|
|
if(!result) { /* resolved right away, either sync or from dnscache */
|
|
DEBUGASSERT(*pdns);
|
|
return CURLE_OK;
|
|
}
|
|
else if(result == CURLE_AGAIN) { /* async resolv in progress */
|
|
return CURLE_OK;
|
|
}
|
|
else if(result == CURLE_OPERATION_TIMEDOUT) { /* took too long */
|
|
failf(data, "Failed to resolve '%s' with timeout after %"
|
|
FMT_TIMEDIFF_T " ms", ctx->hostname,
|
|
curlx_ptimediff_ms(Curl_pgrs_now(data),
|
|
&data->progress.t_startsingle));
|
|
return CURLE_OPERATION_TIMEDOUT;
|
|
}
|
|
else {
|
|
DEBUGASSERT(result);
|
|
failf(data, "Could not resolve: %s", ctx->hostname);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
static CURLcode cf_dns_connect(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
bool *done)
|
|
{
|
|
struct cf_dns_ctx *ctx = cf->ctx;
|
|
|
|
if(cf->connected) {
|
|
*done = TRUE;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
*done = FALSE;
|
|
if(!ctx->started) {
|
|
ctx->started = TRUE;
|
|
ctx->resolv_result = cf_dns_start(cf, data, &ctx->dns);
|
|
}
|
|
|
|
if(!ctx->dns && !ctx->resolv_result) {
|
|
ctx->resolv_result =
|
|
Curl_resolv_take_result(data, ctx->resolv_id, &ctx->dns);
|
|
if(!ctx->dns && !ctx->resolv_result)
|
|
CURL_TRC_CF(data, cf, "DNS resolution ongoing for %s:%u",
|
|
ctx->hostname, ctx->port);
|
|
}
|
|
|
|
if(ctx->resolv_result) {
|
|
CURL_TRC_CF(data, cf, "error resolving: %d", ctx->resolv_result);
|
|
return ctx->resolv_result;
|
|
}
|
|
|
|
if(ctx->dns && !ctx->announced) {
|
|
ctx->announced = TRUE;
|
|
if(cf->sockindex == FIRSTSOCKET) {
|
|
cf->conn->bits.dns_resolved = TRUE;
|
|
Curl_pgrsTime(data, TIMER_NAMELOOKUP);
|
|
}
|
|
cf_dns_report(cf, data, ctx->dns);
|
|
}
|
|
|
|
if(cf->next && !cf->next->connected) {
|
|
CURLcode result = Curl_conn_cf_connect(cf->next, data, done);
|
|
CURL_TRC_CF(data, cf, "connect subfilters -> %d, done=%d", result, *done);
|
|
if(result || !*done)
|
|
return result;
|
|
}
|
|
|
|
/* sub filter chain is connected, so are we now.
|
|
* Unlink the DNS entry, it is no longer needed and if it
|
|
* came from a SHARE in `data`, we need to release it under
|
|
* that one's lock. */
|
|
DEBUGASSERT(*done);
|
|
cf->connected = TRUE;
|
|
Curl_resolv_destroy(data, ctx->resolv_id);
|
|
Curl_dns_entry_unlink(data, &ctx->dns);
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static void cf_dns_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
|
|
{
|
|
struct cf_dns_ctx *ctx = cf->ctx;
|
|
|
|
CURL_TRC_CF(data, cf, "destroy");
|
|
cf_dns_ctx_destroy(data, ctx);
|
|
}
|
|
|
|
static void cf_dns_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
|
{
|
|
cf->connected = FALSE;
|
|
if(cf->next)
|
|
cf->next->cft->do_close(cf->next, data);
|
|
}
|
|
|
|
static CURLcode cf_dns_adjust_pollset(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
struct easy_pollset *ps)
|
|
{
|
|
#ifdef USE_CURL_ASYNC
|
|
if(!cf->connected)
|
|
return Curl_resolv_pollset(data, ps);
|
|
#else
|
|
(void)cf;
|
|
(void)data;
|
|
(void)ps;
|
|
#endif
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static CURLcode cf_dns_cntrl(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
int event, int arg1, void *arg2)
|
|
{
|
|
struct cf_dns_ctx *ctx = cf->ctx;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
(void)arg1;
|
|
(void)arg2;
|
|
switch(event) {
|
|
case CF_CTRL_DATA_DONE:
|
|
if(ctx->dns) {
|
|
/* Should only come here when the connect attempt failed and
|
|
* `data` is giving up on it. On a successful connect, we already
|
|
* unlinked the DNS entry. */
|
|
Curl_dns_entry_unlink(data, &ctx->dns);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
struct Curl_cftype Curl_cft_dns = {
|
|
"DNS",
|
|
CF_TYPE_SETUP,
|
|
CURL_LOG_LVL_NONE,
|
|
cf_dns_destroy,
|
|
cf_dns_connect,
|
|
cf_dns_close,
|
|
Curl_cf_def_shutdown,
|
|
cf_dns_adjust_pollset,
|
|
Curl_cf_def_data_pending,
|
|
Curl_cf_def_send,
|
|
Curl_cf_def_recv,
|
|
cf_dns_cntrl,
|
|
Curl_cf_def_conn_is_alive,
|
|
Curl_cf_def_conn_keep_alive,
|
|
Curl_cf_def_query,
|
|
};
|
|
|
|
static CURLcode cf_dns_create(struct Curl_cfilter **pcf,
|
|
struct Curl_easy *data,
|
|
uint8_t dns_queries,
|
|
const char *hostname,
|
|
uint16_t port,
|
|
uint8_t transport,
|
|
bool abstract_unix_socket,
|
|
struct Curl_dns_entry *dns)
|
|
{
|
|
struct Curl_cfilter *cf = NULL;
|
|
struct cf_dns_ctx *ctx;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
(void)data;
|
|
ctx = cf_dns_ctx_create(data, dns_queries, hostname, port, transport,
|
|
abstract_unix_socket, dns);
|
|
if(!ctx) {
|
|
result = CURLE_OUT_OF_MEMORY;
|
|
goto out;
|
|
}
|
|
|
|
result = Curl_cf_create(&cf, &Curl_cft_dns, ctx);
|
|
|
|
out:
|
|
*pcf = result ? NULL : cf;
|
|
if(result)
|
|
cf_dns_ctx_destroy(data, ctx);
|
|
return result;
|
|
}
|
|
|
|
/* Create a "resolv" filter for the transfer's connection. Figures
|
|
* out the hostname/path and port where to connect to. */
|
|
static CURLcode cf_dns_conn_create(struct Curl_cfilter **pcf,
|
|
struct Curl_easy *data,
|
|
uint8_t dns_queries,
|
|
uint8_t transport,
|
|
struct Curl_dns_entry *dns)
|
|
{
|
|
struct connectdata *conn = data->conn;
|
|
const char *hostname = NULL;
|
|
uint16_t port = 0;
|
|
bool abstract_unix_socket = FALSE;
|
|
|
|
#ifdef USE_UNIX_SOCKETS
|
|
{
|
|
const char *unix_path = Curl_conn_get_unix_path(conn);
|
|
if(unix_path) {
|
|
DEBUGASSERT(transport == TRNSPRT_UNIX);
|
|
hostname = unix_path;
|
|
abstract_unix_socket = (bool)conn->bits.abstract_unix_socket;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifndef CURL_DISABLE_PROXY
|
|
if(!hostname && CONN_IS_PROXIED(conn)) {
|
|
struct hostname *ehost;
|
|
ehost = conn->bits.socksproxy ? &conn->socks_proxy.host :
|
|
&conn->http_proxy.host;
|
|
hostname = ehost->name;
|
|
port = conn->bits.socksproxy ? conn->socks_proxy.port :
|
|
conn->http_proxy.port;
|
|
}
|
|
#endif
|
|
if(!hostname) {
|
|
struct hostname *ehost;
|
|
ehost = conn->bits.conn_to_host ? &conn->conn_to_host : &conn->host;
|
|
/* If not connecting via a proxy, extract the port from the URL, if it is
|
|
* there, thus overriding any defaults that might have been set above. */
|
|
hostname = ehost->name;
|
|
port = conn->bits.conn_to_port ?
|
|
conn->conn_to_port : (uint16_t)conn->remote_port;
|
|
}
|
|
|
|
if(!hostname) {
|
|
DEBUGASSERT(0);
|
|
return CURLE_FAILED_INIT;
|
|
}
|
|
return cf_dns_create(pcf, data, dns_queries,
|
|
hostname, port, transport,
|
|
abstract_unix_socket, dns);
|
|
}
|
|
|
|
/* Adds a "resolv" filter at the top of the connection's filter chain.
|
|
* For FIRSTSOCKET, the `dns` parameter may be NULL. The filter will
|
|
* figure out hostname and port to connect to and start the DNS resolve
|
|
* on the first connect attempt.
|
|
* For SECONDARYSOCKET, the `dns` parameter must be given.
|
|
*/
|
|
CURLcode Curl_cf_dns_add(struct Curl_easy *data,
|
|
struct connectdata *conn,
|
|
int sockindex,
|
|
uint8_t dns_queries,
|
|
uint8_t transport,
|
|
struct Curl_dns_entry *dns)
|
|
{
|
|
struct Curl_cfilter *cf = NULL;
|
|
CURLcode result;
|
|
|
|
DEBUGASSERT(data);
|
|
if(sockindex == FIRSTSOCKET)
|
|
result = cf_dns_conn_create(&cf, data, dns_queries, transport, dns);
|
|
else if(dns) {
|
|
result = cf_dns_create(&cf, data, dns_queries,
|
|
dns->hostname, dns->port, transport,
|
|
FALSE, dns);
|
|
}
|
|
else {
|
|
DEBUGASSERT(0);
|
|
result = CURLE_FAILED_INIT;
|
|
}
|
|
if(result)
|
|
goto out;
|
|
Curl_conn_cf_add(data, conn, sockindex, cf);
|
|
out:
|
|
return result;
|
|
}
|
|
|
|
/* Insert a new "resolv" filter directly after `cf`. It will
|
|
* start a DNS resolve for the given hostnmae and port on the
|
|
* first connect attempt.
|
|
* See socks.c on how this is used to make a non-blocking DNS
|
|
* resolve during connect.
|
|
*/
|
|
CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at,
|
|
struct Curl_easy *data,
|
|
uint8_t dns_queries,
|
|
const char *hostname,
|
|
uint16_t port,
|
|
uint8_t transport)
|
|
{
|
|
struct Curl_cfilter *cf;
|
|
CURLcode result;
|
|
|
|
result = cf_dns_create(&cf, data, dns_queries,
|
|
hostname, port, transport,
|
|
FALSE, NULL);
|
|
if(result)
|
|
return result;
|
|
|
|
Curl_conn_cf_insert_after(cf_at, cf);
|
|
return CURLE_OK;
|
|
}
|
|
|
|
/* Return the resolv result from the first "resolv" filter, starting
|
|
* the given filter `cf` downwards.
|
|
*/
|
|
CURLcode Curl_cf_dns_result(struct Curl_cfilter *cf)
|
|
{
|
|
for(; cf; cf = cf->next) {
|
|
if(cf->cft == &Curl_cft_dns) {
|
|
struct cf_dns_ctx *ctx = cf->ctx;
|
|
if(ctx->dns || ctx->resolv_result)
|
|
return ctx->resolv_result;
|
|
return CURLE_AGAIN;
|
|
}
|
|
}
|
|
return CURLE_FAILED_INIT;
|
|
}
|
|
|
|
/* Return the result of the DNS resolution. Searches for a "resolv"
|
|
* filter from the top of the filter chain down. Returns
|
|
* - CURLE_AGAIN when not done yet
|
|
* - CURLE_OK when DNS was successfully resolved
|
|
* - CURLR_FAILED_INIT when no resolv filter was found
|
|
* - error returned by the DNS resolv
|
|
*/
|
|
CURLcode Curl_conn_dns_result(struct connectdata *conn, int sockindex)
|
|
{
|
|
return Curl_cf_dns_result(conn->cfilter[sockindex]);
|
|
}
|
|
|
|
static const struct Curl_addrinfo *
|
|
cf_dns_get_nth_ai(const struct Curl_addrinfo *ai,
|
|
int ai_family, unsigned int index)
|
|
{
|
|
unsigned int i = 0;
|
|
for(i = 0; ai; ai = ai->ai_next) {
|
|
if(ai->ai_family == ai_family) {
|
|
if(i == index)
|
|
return ai;
|
|
++i;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool Curl_conn_dns_has_any_ai(struct Curl_easy *data, int sockindex)
|
|
{
|
|
struct Curl_cfilter *cf = data->conn->cfilter[sockindex];
|
|
|
|
(void)data;
|
|
for(; cf; cf = cf->next) {
|
|
if(cf->cft == &Curl_cft_dns) {
|
|
struct cf_dns_ctx *ctx = cf->ctx;
|
|
if(ctx->resolv_result)
|
|
return FALSE;
|
|
else if(ctx->dns)
|
|
return !!ctx->dns->addr;
|
|
else
|
|
#ifdef USE_IPV6
|
|
return Curl_resolv_get_ai(data, ctx->resolv_id, AF_INET, 0) ||
|
|
Curl_resolv_get_ai(data, ctx->resolv_id, AF_INET6, 0);
|
|
#else
|
|
return !!Curl_resolv_get_ai(data, ctx->resolv_id, AF_INET, 0);
|
|
#endif
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* Return the addrinfo at `index` for the given `family` from the
|
|
* first "resolve" filter underneath `cf`. If the DNS resolving is
|
|
* not done yet or if no address for the family exists, returns NULL.
|
|
*/
|
|
const struct Curl_addrinfo *
|
|
Curl_cf_dns_get_ai(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
int ai_family,
|
|
unsigned int index)
|
|
{
|
|
(void)data;
|
|
for(; cf; cf = cf->next) {
|
|
if(cf->cft == &Curl_cft_dns) {
|
|
struct cf_dns_ctx *ctx = cf->ctx;
|
|
if(ctx->resolv_result)
|
|
return NULL;
|
|
else if(ctx->dns)
|
|
return cf_dns_get_nth_ai(ctx->dns->addr, ai_family, index);
|
|
else
|
|
return Curl_resolv_get_ai(data, ctx->resolv_id, ai_family, index);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Return the addrinfo at `index` for the given `family` from the
|
|
* first "resolve" filter at the connection. If the DNS resolving is
|
|
* not done yet or if no address for the family exists, returns NULL.
|
|
*/
|
|
const struct Curl_addrinfo *
|
|
Curl_conn_dns_get_ai(struct Curl_easy *data,
|
|
int sockindex,
|
|
int ai_family,
|
|
unsigned int index)
|
|
{
|
|
struct connectdata *conn = data->conn;
|
|
return Curl_cf_dns_get_ai(conn->cfilter[sockindex], data,
|
|
ai_family, index);
|
|
}
|
|
|
|
#ifdef USE_HTTPSRR
|
|
/* Return the HTTPS-RR info from the first "resolve" filter at the
|
|
* connection. If the DNS resolving is not done yet or if there
|
|
* is no HTTPS-RR info, returns NULL.
|
|
*/
|
|
const struct Curl_https_rrinfo *
|
|
Curl_conn_dns_get_https(struct Curl_easy *data, int sockindex)
|
|
{
|
|
struct Curl_cfilter *cf = data->conn->cfilter[sockindex];
|
|
for(; cf; cf = cf->next) {
|
|
if(cf->cft == &Curl_cft_dns) {
|
|
struct cf_dns_ctx *ctx = cf->ctx;
|
|
if(ctx->dns)
|
|
return ctx->dns->hinfo;
|
|
else
|
|
return Curl_resolv_get_https(data, ctx->resolv_id);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool Curl_conn_dns_resolved_https(struct Curl_easy *data, int sockindex)
|
|
{
|
|
struct Curl_cfilter *cf = data->conn->cfilter[sockindex];
|
|
for(; cf; cf = cf->next) {
|
|
if(cf->cft == &Curl_cft_dns) {
|
|
struct cf_dns_ctx *ctx = cf->ctx;
|
|
if(ctx->dns)
|
|
return TRUE;
|
|
else
|
|
return Curl_resolv_knows_https(data, ctx->resolv_id);
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
#endif /* USE_HTTPSRR */
|