in libs/curl/lib/url.c [3329:3725]
static CURLcode create_conn(struct Curl_easy *data,
struct connectdata **in_connect,
bool *async)
{
CURLcode result = CURLE_OK;
struct connectdata *conn;
struct connectdata *existing = NULL;
bool reuse;
bool connections_available = TRUE;
bool force_reuse = FALSE;
bool waitpipe = FALSE;
size_t max_host_connections = Curl_multi_max_host_connections(data->multi);
size_t max_total_connections = Curl_multi_max_total_connections(data->multi);
*async = FALSE;
*in_connect = NULL;
/*************************************************************
* Check input data
*************************************************************/
if(!data->state.url) {
result = CURLE_URL_MALFORMAT;
goto out;
}
/* First, split up the current URL in parts so that we can use the
parts for checking against the already present connections. In order
to not have to modify everything at once, we allocate a temporary
connection data struct and fill in for comparison purposes. */
conn = allocate_conn(data);
if(!conn) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
/* We must set the return variable as soon as possible, so that our
parent can cleanup any possible allocs we may have done before
any failure */
*in_connect = conn;
result = parseurlandfillconn(data, conn);
if(result)
goto out;
if(data->set.str[STRING_SASL_AUTHZID]) {
conn->sasl_authzid = strdup(data->set.str[STRING_SASL_AUTHZID]);
if(!conn->sasl_authzid) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
}
if(data->set.str[STRING_BEARER]) {
conn->oauth_bearer = strdup(data->set.str[STRING_BEARER]);
if(!conn->oauth_bearer) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
}
#ifdef USE_UNIX_SOCKETS
if(data->set.str[STRING_UNIX_SOCKET_PATH]) {
conn->unix_domain_socket = strdup(data->set.str[STRING_UNIX_SOCKET_PATH]);
if(!conn->unix_domain_socket) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
conn->bits.abstract_unix_socket = data->set.abstract_unix_socket;
}
#endif
/* After the unix socket init but before the proxy vars are used, parse and
initialize the proxy vars */
#ifndef CURL_DISABLE_PROXY
result = create_conn_helper_init_proxy(data, conn);
if(result)
goto out;
/*************************************************************
* If the protocol is using SSL and HTTP proxy is used, we set
* the tunnel_proxy bit.
*************************************************************/
if((conn->given->flags&PROTOPT_SSL) && conn->bits.httpproxy)
conn->bits.tunnel_proxy = TRUE;
#endif
/*************************************************************
* Figure out the remote port number and fix it in the URL
*************************************************************/
result = parse_remote_port(data, conn);
if(result)
goto out;
/* Check for overridden login details and set them accordingly so that
they are known when protocol->setup_connection is called! */
result = override_login(data, conn);
if(result)
goto out;
result = set_login(data, conn); /* default credentials */
if(result)
goto out;
/*************************************************************
* Process the "connect to" linked list of hostname/port mappings.
* Do this after the remote port number has been fixed in the URL.
*************************************************************/
result = parse_connect_to_slist(data, conn, data->set.connect_to);
if(result)
goto out;
/*************************************************************
* IDN-convert the proxy hostnames
*************************************************************/
#ifndef CURL_DISABLE_PROXY
if(conn->bits.httpproxy) {
result = Curl_idnconvert_hostname(&conn->http_proxy.host);
if(result)
return result;
}
if(conn->bits.socksproxy) {
result = Curl_idnconvert_hostname(&conn->socks_proxy.host);
if(result)
return result;
}
#endif
if(conn->bits.conn_to_host) {
result = Curl_idnconvert_hostname(&conn->conn_to_host);
if(result)
return result;
}
/*************************************************************
* Check whether the host and the "connect to host" are equal.
* Do this after the hostnames have been IDN-converted.
*************************************************************/
if(conn->bits.conn_to_host &&
strcasecompare(conn->conn_to_host.name, conn->host.name)) {
conn->bits.conn_to_host = FALSE;
}
/*************************************************************
* Check whether the port and the "connect to port" are equal.
* Do this after the remote port number has been fixed in the URL.
*************************************************************/
if(conn->bits.conn_to_port && conn->conn_to_port == conn->remote_port) {
conn->bits.conn_to_port = FALSE;
}
#ifndef CURL_DISABLE_PROXY
/*************************************************************
* If the "connect to" feature is used with an HTTP proxy,
* we set the tunnel_proxy bit.
*************************************************************/
if((conn->bits.conn_to_host || conn->bits.conn_to_port) &&
conn->bits.httpproxy)
conn->bits.tunnel_proxy = TRUE;
#endif
/*************************************************************
* Setup internals depending on protocol. Needs to be done after
* we figured out what/if proxy to use.
*************************************************************/
result = setup_connection_internals(data, conn);
if(result)
goto out;
/***********************************************************************
* file: is a special case in that it does not need a network connection
***********************************************************************/
#ifndef CURL_DISABLE_FILE
if(conn->handler->flags & PROTOPT_NONETWORK) {
bool done;
/* this is supposed to be the connect function so we better at least check
that the file is present here! */
DEBUGASSERT(conn->handler->connect_it);
Curl_persistconninfo(data, conn, NULL);
result = conn->handler->connect_it(data, &done);
/* Setup a "faked" transfer that will do nothing */
if(!result) {
Curl_attach_connection(data, conn);
result = Curl_conncache_add_conn(data);
if(result)
goto out;
/*
* Setup whatever necessary for a resumed transfer
*/
result = setup_range(data);
if(result) {
DEBUGASSERT(conn->handler->done);
/* we ignore the return code for the protocol-specific DONE */
(void)conn->handler->done(data, result, FALSE);
goto out;
}
Curl_xfer_setup_nop(data);
}
/* since we skip do_init() */
Curl_init_do(data, conn);
goto out;
}
#endif
/* Setup filter for network connections */
conn->recv[FIRSTSOCKET] = Curl_cf_recv;
conn->send[FIRSTSOCKET] = Curl_cf_send;
conn->recv[SECONDARYSOCKET] = Curl_cf_recv;
conn->send[SECONDARYSOCKET] = Curl_cf_send;
conn->bits.tcp_fastopen = data->set.tcp_fastopen;
/* Complete the easy's SSL configuration for connection cache matching */
result = Curl_ssl_easy_config_complete(data);
if(result)
goto out;
prune_dead_connections(data);
/*************************************************************
* Check the current list of connections to see if we can
* reuse an already existing one or if we have to create a
* new one.
*************************************************************/
DEBUGASSERT(conn->user);
DEBUGASSERT(conn->passwd);
/* reuse_fresh is TRUE if we are told to use a new connection by force, but
we only acknowledge this option if this is not a reused connection
already (which happens due to follow-location or during an HTTP
authentication phase). CONNECT_ONLY transfers also refuse reuse. */
if((data->set.reuse_fresh && !data->state.followlocation) ||
data->set.connect_only)
reuse = FALSE;
else
reuse = ConnectionExists(data, conn, &existing, &force_reuse, &waitpipe);
if(reuse) {
/*
* We already have a connection for this, we got the former connection in
* `existing` and thus we need to cleanup the one we just
* allocated before we can move along and use `existing`.
*/
reuse_conn(data, conn, existing);
conn = existing;
*in_connect = conn;
#ifndef CURL_DISABLE_PROXY
infof(data, "Re-using existing connection with %s %s",
conn->bits.proxy?"proxy":"host",
conn->socks_proxy.host.name ? conn->socks_proxy.host.dispname :
conn->http_proxy.host.name ? conn->http_proxy.host.dispname :
conn->host.dispname);
#else
infof(data, "Re-using existing connection with host %s",
conn->host.dispname);
#endif
}
else {
/* We have decided that we want a new connection. However, we may not
be able to do that if we have reached the limit of how many
connections we are allowed to open. */
if(conn->handler->flags & PROTOPT_ALPN) {
/* The protocol wants it, so set the bits if enabled in the easy handle
(default) */
if(data->set.ssl_enable_alpn)
conn->bits.tls_enable_alpn = TRUE;
}
if(waitpipe)
/* There is a connection that *might* become usable for multiplexing
"soon", and we wait for that */
connections_available = FALSE;
else {
/* this gets a lock on the conncache */
struct connectbundle *bundle =
Curl_conncache_find_bundle(data, conn, data->state.conn_cache);
if(max_host_connections > 0 && bundle &&
(bundle->num_connections >= max_host_connections)) {
struct connectdata *conn_candidate;
/* The bundle is full. Extract the oldest connection. */
conn_candidate = Curl_conncache_extract_bundle(data, bundle);
CONNCACHE_UNLOCK(data);
if(conn_candidate)
Curl_disconnect(data, conn_candidate, FALSE);
else {
infof(data, "No more connections allowed to host: %zu",
max_host_connections);
connections_available = FALSE;
}
}
else
CONNCACHE_UNLOCK(data);
}
if(connections_available &&
(max_total_connections > 0) &&
(Curl_conncache_size(data) >= max_total_connections)) {
struct connectdata *conn_candidate;
/* The cache is full. Let's see if we can kill a connection. */
conn_candidate = Curl_conncache_extract_oldest(data);
if(conn_candidate)
Curl_disconnect(data, conn_candidate, FALSE);
else
#ifndef CURL_DISABLE_DOH
if(data->set.dohfor)
infof(data, "Allowing DoH to override max connection limit");
else
#endif
{
infof(data, "No connections available in cache");
connections_available = FALSE;
}
}
if(!connections_available) {
infof(data, "No connections available.");
Curl_conn_free(data, conn);
*in_connect = NULL;
result = CURLE_NO_CONNECTION_AVAILABLE;
goto out;
}
else {
/*
* This is a brand new connection, so let's store it in the connection
* cache of ours!
*/
result = Curl_ssl_conn_config_init(data, conn);
if(result) {
DEBUGF(fprintf(stderr, "Error: init connection ssl config\n"));
goto out;
}
Curl_attach_connection(data, conn);
result = Curl_conncache_add_conn(data);
if(result)
goto out;
}
#if defined(USE_NTLM)
/* If NTLM is requested in a part of this connection, make sure we do not
assume the state is fine as this is a fresh connection and NTLM is
connection based. */
if((data->state.authhost.picked & CURLAUTH_NTLM) &&
data->state.authhost.done) {
infof(data, "NTLM picked AND auth done set, clear picked");
data->state.authhost.picked = CURLAUTH_NONE;
data->state.authhost.done = FALSE;
}
if((data->state.authproxy.picked & CURLAUTH_NTLM) &&
data->state.authproxy.done) {
infof(data, "NTLM-proxy picked AND auth done set, clear picked");
data->state.authproxy.picked = CURLAUTH_NONE;
data->state.authproxy.done = FALSE;
}
#endif
}
/* Setup and init stuff before DO starts, in preparing for the transfer. */
Curl_init_do(data, conn);
/*
* Setup whatever necessary for a resumed transfer
*/
result = setup_range(data);
if(result)
goto out;
/* Continue connectdata initialization here. */
/*************************************************************
* Resolve the address of the server or proxy
*************************************************************/
result = resolve_server(data, conn, async);
if(result)
goto out;
/* Everything general done, inform filters that they need
* to prepare for a data transfer.
*/
result = Curl_conn_ev_data_setup(data);
out:
return result;
}