#include "curl_setup.h"
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif
#ifdef HAVE_IPHLPAPI_H
#include <Iphlpapi.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef __VMS
#include <in.h>
#include <inet.h>
#endif
#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#ifndef HAVE_SOCKET
#error "We cannot compile without socket() support!"
#endif
#if defined(HAVE_IF_NAMETOINDEX) && defined(_WIN32)
#if defined(__MINGW32__) && (__MINGW64_VERSION_MAJOR <= 5)
#include <wincrypt.h>
#endif
#include <iphlpapi.h>
#endif
#include <limits.h>
#include "doh.h"
#include "urldata.h"
#include "netrc.h"
#include "formdata.h"
#include "mime.h"
#include "vtls/vtls.h"
#include "hostip.h"
#include "transfer.h"
#include "sendf.h"
#include "progress.h"
#include "cookie.h"
#include "strcase.h"
#include "escape.h"
#include "share.h"
#include "content_encoding.h"
#include "http_digest.h"
#include "http_negotiate.h"
#include "select.h"
#include "multiif.h"
#include "easyif.h"
#include "speedcheck.h"
#include "curlx/warnless.h"
#include "getinfo.h"
#include "pop3.h"
#include "urlapi-int.h"
#include "system_win32.h"
#include "hsts.h"
#include "noproxy.h"
#include "cfilters.h"
#include "idn.h"
#include "ftp.h"
#include "dict.h"
#include "telnet.h"
#include "tftp.h"
#include "http.h"
#include "http2.h"
#include "file.h"
#include "curl_ldap.h"
#include "vssh/ssh.h"
#include "imap.h"
#include "url.h"
#include "connect.h"
#include "http_ntlm.h"
#include "curl_rtmp.h"
#include "gopher.h"
#include "mqtt.h"
#include "http_proxy.h"
#include "conncache.h"
#include "multihandle.h"
#include "strdup.h"
#include "setopt.h"
#include "altsvc.h"
#include "curlx/dynbuf.h"
#include "headers.h"
#include "curlx/strerr.h"
#include "curlx/strparse.h"
#include "curl_memory.h"
#include "memdebug.h"
#ifdef USE_NGHTTP2
static void data_priority_cleanup(struct Curl_easy *data);
#else
#define data_priority_cleanup(x)
#endif
#if READBUFFER_SIZE < READBUFFER_MIN
# error READBUFFER_SIZE is too small
#endif
#ifdef USE_UNIX_SOCKETS
#define UNIX_SOCKET_PREFIX "localhost"
#endif
#define MAX_URL_LEN 0xffff
static curl_prot_t get_protocol_family(const struct Curl_handler *h)
{
DEBUGASSERT(h);
DEBUGASSERT(h->family);
return h->family;
}
void Curl_freeset(struct Curl_easy *data)
{
enum dupstring i;
enum dupblob j;
for(i = (enum dupstring)0; i < STRING_LAST; i++) {
Curl_safefree(data->set.str[i]);
}
for(j = (enum dupblob)0; j < BLOB_LAST; j++) {
Curl_safefree(data->set.blobs[j]);
}
if(data->state.referer_alloc) {
Curl_safefree(data->state.referer);
data->state.referer_alloc = FALSE;
}
data->state.referer = NULL;
if(data->state.url_alloc) {
Curl_safefree(data->state.url);
data->state.url_alloc = FALSE;
}
data->state.url = NULL;
Curl_mime_cleanpart(&data->set.mimepost);
#ifndef CURL_DISABLE_COOKIES
curl_slist_free_all(data->state.cookielist);
data->state.cookielist = NULL;
#endif
}
static void up_free(struct Curl_easy *data)
{
struct urlpieces *up = &data->state.up;
Curl_safefree(up->scheme);
Curl_safefree(up->hostname);
Curl_safefree(up->port);
Curl_safefree(up->user);
Curl_safefree(up->password);
Curl_safefree(up->options);
Curl_safefree(up->path);
Curl_safefree(up->query);
curl_url_cleanup(data->state.uh);
data->state.uh = NULL;
}
CURLcode Curl_close(struct Curl_easy **datap)
{
struct Curl_easy *data;
if(!datap || !*datap)
return CURLE_OK;
data = *datap;
*datap = NULL;
if(!data->state.internal && data->multi) {
curl_multi_remove_handle(data->multi, data);
}
else {
Curl_detach_connection(data);
if(!data->state.internal && data->multi_easy) {
curl_multi_cleanup(data->multi_easy);
data->multi_easy = NULL;
}
}
DEBUGASSERT(!data->conn || data->state.internal);
Curl_expire_clear(data);
if(data->state.rangestringalloc)
free(data->state.range);
Curl_async_destroy(data);
Curl_resolv_unlink(data, &data->state.dns[0]);
Curl_resolv_unlink(data, &data->state.dns[1]);
data->set.verbose = FALSE;
data->magic = 0;
Curl_req_free(&data->req, data);
Curl_ssl_close_all(data);
Curl_safefree(data->state.first_host);
Curl_ssl_free_certinfo(data);
if(data->state.referer_alloc) {
Curl_safefree(data->state.referer);
data->state.referer_alloc = FALSE;
}
data->state.referer = NULL;
up_free(data);
curlx_dyn_free(&data->state.headerb);
Curl_flush_cookies(data, TRUE);
#ifndef CURL_DISABLE_ALTSVC
Curl_altsvc_save(data, data->asi, data->set.str[STRING_ALTSVC]);
Curl_altsvc_cleanup(&data->asi);
#endif
#ifndef CURL_DISABLE_HSTS
Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]);
if(!data->share || !data->share->hsts)
Curl_hsts_cleanup(&data->hsts);
curl_slist_free_all(data->state.hstslist);
#endif
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH)
Curl_http_auth_cleanup_digest(data);
#endif
Curl_safefree(data->state.most_recent_ftp_entrypath);
Curl_safefree(data->info.contenttype);
Curl_safefree(data->info.wouldredirect);
data_priority_cleanup(data);
if(data->share) {
Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE);
data->share->dirty--;
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
Curl_safefree(data->state.aptr.uagent);
Curl_safefree(data->state.aptr.userpwd);
Curl_safefree(data->state.aptr.accept_encoding);
Curl_safefree(data->state.aptr.rangeline);
Curl_safefree(data->state.aptr.ref);
Curl_safefree(data->state.aptr.host);
#ifndef CURL_DISABLE_COOKIES
Curl_safefree(data->state.aptr.cookiehost);
#endif
#ifndef CURL_DISABLE_RTSP
Curl_safefree(data->state.aptr.rtsp_transport);
#endif
Curl_safefree(data->state.aptr.user);
Curl_safefree(data->state.aptr.passwd);
#ifndef CURL_DISABLE_PROXY
Curl_safefree(data->state.aptr.proxyuser);
Curl_safefree(data->state.aptr.proxypasswd);
#endif
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_FORM_API)
Curl_mime_cleanpart(data->state.formp);
Curl_safefree(data->state.formp);
#endif
Curl_wildcard_dtor(&data->wildcard);
Curl_freeset(data);
Curl_headers_cleanup(data);
Curl_netrc_cleanup(&data->state.netrc);
free(data);
return CURLE_OK;
}
void Curl_init_userdefined(struct Curl_easy *data)
{
struct UserDefined *set = &data->set;
set->out = stdout;
set->in_set = stdin;
set->err = stderr;
#if defined(__clang__) && __clang_major__ >= 16
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcast-function-type-strict"
#endif
set->fwrite_func = (curl_write_callback)fwrite;
set->fread_func_set = (curl_read_callback)fread;
#if defined(__clang__) && __clang_major__ >= 16
#pragma clang diagnostic pop
#endif
set->is_fread_set = 0;
set->seek_client = ZERO_NULL;
set->filesize = -1;
set->postfieldsize = -1;
set->maxredirs = 30;
set->method = HTTPREQ_GET;
#ifndef CURL_DISABLE_RTSP
set->rtspreq = RTSPREQ_OPTIONS;
#endif
#ifndef CURL_DISABLE_FTP
set->ftp_use_epsv = TRUE;
set->ftp_use_eprt = TRUE;
set->ftp_use_pret = FALSE;
set->ftp_filemethod = FTPFILE_MULTICWD;
set->ftp_skip_ip = TRUE;
#endif
set->dns_cache_timeout_ms = 60000;
set->general_ssl.ca_cache_timeout = 24 * 60 * 60;
set->httpauth = CURLAUTH_BASIC;
#ifndef CURL_DISABLE_PROXY
set->proxyport = 0;
set->proxytype = CURLPROXY_HTTP;
set->proxyauth = CURLAUTH_BASIC;
set->socks5auth = CURLAUTH_BASIC | CURLAUTH_GSSAPI;
#endif
Curl_mime_initpart(&set->mimepost);
Curl_ssl_easy_config_init(data);
#ifndef CURL_DISABLE_DOH
set->doh_verifyhost = TRUE;
set->doh_verifypeer = TRUE;
#endif
#ifdef USE_SSH
set->ssh_auth_types = CURLSSH_AUTH_DEFAULT;
set->new_directory_perms = 0755;
#endif
set->new_file_perms = 0644;
set->allowed_protocols = (curl_prot_t) CURLPROTO_ALL;
set->redir_protocols = CURLPROTO_REDIR;
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
set->socks5_gssapi_nec = FALSE;
#endif
#ifdef USE_SSL
Curl_setopt_SSLVERSION(data, CURLOPT_SSLVERSION, CURL_SSLVERSION_DEFAULT);
#ifndef CURL_DISABLE_PROXY
Curl_setopt_SSLVERSION(data, CURLOPT_PROXY_SSLVERSION,
CURL_SSLVERSION_DEFAULT);
#endif
#endif
#ifndef CURL_DISABLE_FTP
set->wildcard_enabled = FALSE;
set->chunk_bgn = ZERO_NULL;
set->chunk_end = ZERO_NULL;
set->fnmatch = ZERO_NULL;
#endif
set->tcp_keepalive = FALSE;
set->tcp_keepintvl = 60;
set->tcp_keepidle = 60;
set->tcp_keepcnt = 9;
set->tcp_fastopen = FALSE;
set->tcp_nodelay = TRUE;
set->ssl_enable_alpn = TRUE;
set->expect_100_timeout = 1000L;
set->sep_headers = TRUE;
set->buffer_size = READBUFFER_SIZE;
set->upload_buffer_size = UPLOADBUFFER_DEFAULT;
set->happy_eyeballs_timeout = CURL_HET_DEFAULT;
set->upkeep_interval_ms = CURL_UPKEEP_INTERVAL_DEFAULT;
set->maxconnects = DEFAULT_CONNCACHE_SIZE;
set->conn_max_idle_ms = 118 * 1000;
set->conn_max_age_ms = 24 * 3600 * 1000;
set->http09_allowed = FALSE;
set->httpwant = CURL_HTTP_VERSION_NONE
;
#if defined(USE_HTTP2) || defined(USE_HTTP3)
memset(&set->priority, 0, sizeof(set->priority));
#endif
set->quick_exit = 0L;
#ifndef CURL_DISABLE_WEBSOCKETS
set->ws_raw_mode = FALSE;
set->ws_no_auto_pong = FALSE;
#endif
}
static void easy_meta_freeentry(void *p)
{
(void)p;
DEBUGASSERT(p == NULL);
}
CURLcode Curl_open(struct Curl_easy **curl)
{
struct Curl_easy *data;
data = calloc(1, sizeof(struct Curl_easy));
if(!data) {
DEBUGF(curl_mfprintf(stderr, "Error: calloc of Curl_easy failed\n"));
return CURLE_OUT_OF_MEMORY;
}
data->magic = CURLEASY_MAGIC_NUMBER;
data->state.lastconnect_id = -1;
data->state.recent_conn_id = -1;
data->id = -1;
data->mid = UINT_MAX;
data->master_mid = UINT_MAX;
data->progress.hide = TRUE;
data->state.current_speed = -1;
Curl_hash_init(&data->meta_hash, 23,
Curl_hash_str, curlx_str_key_compare, easy_meta_freeentry);
curlx_dyn_init(&data->state.headerb, CURL_MAX_HTTP_HEADER);
Curl_req_init(&data->req);
Curl_initinfo(data);
#ifndef CURL_DISABLE_HTTP
Curl_llist_init(&data->state.httphdrs, NULL);
#endif
Curl_netrc_init(&data->state.netrc);
Curl_init_userdefined(data);
*curl = data;
return CURLE_OK;
}
void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn)
{
size_t i;
DEBUGASSERT(conn);
if(conn->handler && conn->handler->disconnect &&
!conn->bits.shutdown_handler)
conn->handler->disconnect(data, conn, TRUE);
for(i = 0; i < CURL_ARRAYSIZE(conn->cfilter); ++i) {
Curl_conn_cf_discard_all(data, conn, (int)i);
}
Curl_free_idnconverted_hostname(&conn->host);
Curl_free_idnconverted_hostname(&conn->conn_to_host);
#ifndef CURL_DISABLE_PROXY
Curl_free_idnconverted_hostname(&conn->http_proxy.host);
Curl_free_idnconverted_hostname(&conn->socks_proxy.host);
Curl_safefree(conn->http_proxy.user);
Curl_safefree(conn->socks_proxy.user);
Curl_safefree(conn->http_proxy.passwd);
Curl_safefree(conn->socks_proxy.passwd);
Curl_safefree(conn->http_proxy.host.rawalloc);
Curl_safefree(conn->socks_proxy.host.rawalloc);
#endif
Curl_safefree(conn->user);
Curl_safefree(conn->passwd);
Curl_safefree(conn->sasl_authzid);
Curl_safefree(conn->options);
Curl_safefree(conn->oauth_bearer);
Curl_safefree(conn->host.rawalloc);
Curl_safefree(conn->conn_to_host.rawalloc);
Curl_safefree(conn->hostname_resolve);
Curl_safefree(conn->secondaryhostname);
Curl_safefree(conn->localdev);
Curl_ssl_conn_config_cleanup(conn);
#ifdef USE_UNIX_SOCKETS
Curl_safefree(conn->unix_domain_socket);
#endif
Curl_safefree(conn->destination);
Curl_uint_spbset_destroy(&conn->xfers_attached);
Curl_hash_destroy(&conn->meta_hash);
free(conn);
}
static bool xfer_may_multiplex(const struct Curl_easy *data,
const struct connectdata *conn)
{
#ifndef CURL_DISABLE_HTTP
if((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
(!conn->bits.protoconnstart || !conn->bits.close)) {
if(Curl_multiplex_wanted(data->multi) &&
(data->state.http_neg.allowed & (CURL_HTTP_V2x|CURL_HTTP_V3x)))
return TRUE;
}
#else
(void)data;
(void)conn;
#endif
return FALSE;
}
#ifndef CURL_DISABLE_PROXY
static bool
proxy_info_matches(const struct proxy_info *data,
const struct proxy_info *needle)
{
if((data->proxytype == needle->proxytype) &&
(data->port == needle->port) &&
curl_strequal(data->host.name, needle->host.name))
return TRUE;
return FALSE;
}
static bool
socks_proxy_info_matches(const struct proxy_info *data,
const struct proxy_info *needle)
{
if(!proxy_info_matches(data, needle))
return FALSE;
if(Curl_timestrcmp(data->user, needle->user) ||
Curl_timestrcmp(data->passwd, needle->passwd))
return FALSE;
return TRUE;
}
#else
#define proxy_info_matches(x,y) FALSE
#define socks_proxy_info_matches(x,y) FALSE
#endif
static bool conn_maxage(struct Curl_easy *data,
struct connectdata *conn,
struct curltime now)
{
timediff_t age_ms;
if(data->set.conn_max_idle_ms) {
age_ms = curlx_timediff(now, conn->lastused);
if(age_ms > data->set.conn_max_idle_ms) {
infof(data, "Too old connection (%" FMT_TIMEDIFF_T
" ms idle, max idle is %" FMT_TIMEDIFF_T " ms), disconnect it",
age_ms, data->set.conn_max_idle_ms);
return TRUE;
}
}
if(data->set.conn_max_age_ms) {
age_ms = curlx_timediff(now, conn->created);
if(age_ms > data->set.conn_max_age_ms) {
infof(data,
"Too old connection (created %" FMT_TIMEDIFF_T
" ms ago, max lifetime is %" FMT_TIMEDIFF_T " ms), disconnect it",
age_ms, data->set.conn_max_age_ms);
return TRUE;
}
}
return FALSE;
}
bool Curl_conn_seems_dead(struct connectdata *conn,
struct Curl_easy *data,
struct curltime *pnow)
{
DEBUGASSERT(!data->conn);
if(!CONN_INUSE(conn)) {
bool dead;
struct curltime now;
if(!pnow) {
now = curlx_now();
pnow = &now;
}
if(conn_maxage(data, conn, *pnow)) {
dead = TRUE;
}
else if(conn->handler->connection_check) {
unsigned int state;
Curl_attach_connection(data, conn);
state = conn->handler->connection_check(data, conn, CONNCHECK_ISDEAD);
dead = (state & CONNRESULT_DEAD);
Curl_detach_connection(data);
}
else {
bool input_pending = FALSE;
Curl_attach_connection(data, conn);
dead = !Curl_conn_is_alive(data, conn, &input_pending);
if(input_pending) {
DEBUGF(infof(data, "connection has input pending, not reusable"));
dead = TRUE;
}
Curl_detach_connection(data);
}
if(dead) {
infof(data, "Connection %" FMT_OFF_T " seems to be dead",
conn->connection_id);
return TRUE;
}
}
return FALSE;
}
CURLcode Curl_conn_upkeep(struct Curl_easy *data,
struct connectdata *conn,
struct curltime *now)
{
CURLcode result = CURLE_OK;
if(curlx_timediff(*now, conn->keepalive) <= data->set.upkeep_interval_ms)
return result;
Curl_attach_connection(data, conn);
if(conn->handler->connection_check) {
unsigned int rc;
rc = conn->handler->connection_check(data, conn, CONNCHECK_KEEPALIVE);
if(rc & CONNRESULT_DEAD)
result = CURLE_RECV_ERROR;
}
else {
result = Curl_conn_keep_alive(data, conn, FIRSTSOCKET);
}
Curl_detach_connection(data);
conn->keepalive = *now;
return result;
}
#ifdef USE_SSH
static bool ssh_config_matches(struct connectdata *one,
struct connectdata *two)
{
struct ssh_conn *sshc1, *sshc2;
sshc1 = Curl_conn_meta_get(one, CURL_META_SSH_CONN);
sshc2 = Curl_conn_meta_get(two, CURL_META_SSH_CONN);
return (sshc1 && sshc2 && Curl_safecmp(sshc1->rsa, sshc2->rsa) &&
Curl_safecmp(sshc1->rsa_pub, sshc2->rsa_pub));
}
#endif
struct url_conn_match {
struct connectdata *found;
struct Curl_easy *data;
struct connectdata *needle;
BIT(may_multiplex);
BIT(want_ntlm_http);
BIT(want_proxy_ntlm_http);
BIT(wait_pipe);
BIT(force_reuse);
BIT(seen_pending_conn);
BIT(seen_single_use_conn);
BIT(seen_multiplex_conn);
};
static bool url_match_connect_config(struct connectdata *conn,
struct url_conn_match *m)
{
if(conn->connect_only || conn->bits.close || conn->bits.no_reuse)
return FALSE;
if(m->data->set.ipver != CURL_IPRESOLVE_WHATEVER
&& m->data->set.ipver != conn->ip_version)
return FALSE;
if(m->needle->localdev || m->needle->localport) {
if((conn->localport != m->needle->localport) ||
(conn->localportrange != m->needle->localportrange) ||
(m->needle->localdev &&
(!conn->localdev || strcmp(conn->localdev, m->needle->localdev))))
return FALSE;
}
if(m->needle->bits.conn_to_host != conn->bits.conn_to_host)
return FALSE;
if(m->needle->bits.conn_to_port != conn->bits.conn_to_port)
return FALSE;
#ifdef USE_UNIX_SOCKETS
if(m->needle->unix_domain_socket) {
if(!conn->unix_domain_socket)
return FALSE;
if(strcmp(m->needle->unix_domain_socket, conn->unix_domain_socket))
return FALSE;
if(m->needle->bits.abstract_unix_socket != conn->bits.abstract_unix_socket)
return FALSE;
}
else if(conn->unix_domain_socket)
return FALSE;
#endif
return TRUE;
}
static bool url_match_fully_connected(struct connectdata *conn,
struct url_conn_match *m)
{
if(!Curl_conn_is_connected(conn, FIRSTSOCKET) ||
conn->bits.upgrade_in_progress) {
if(m->may_multiplex) {
m->seen_pending_conn = TRUE;
infof(m->data, "Connection #%" FMT_OFF_T
" is not open enough, cannot reuse", conn->connection_id);
}
return FALSE;
}
return TRUE;
}
static bool url_match_multi(struct connectdata *conn,
struct url_conn_match *m)
{
if(CONN_INUSE(conn)) {
DEBUGASSERT(conn->attached_multi);
if(conn->attached_multi != m->data->multi)
return FALSE;
}
return TRUE;
}
static bool url_match_multiplex_needs(struct connectdata *conn,
struct url_conn_match *m)
{
if(CONN_INUSE(conn)) {
if(!conn->bits.multiplex) {
m->seen_single_use_conn = TRUE;
return FALSE;
}
m->seen_multiplex_conn = TRUE;
if(!m->may_multiplex || !url_match_multi(conn, m))
return FALSE;
}
return TRUE;
}
static bool url_match_multiplex_limits(struct connectdata *conn,
struct url_conn_match *m)
{
if(CONN_INUSE(conn) && m->may_multiplex) {
DEBUGASSERT(conn->bits.multiplex);
if(CONN_ATTACHED(conn) >=
Curl_multi_max_concurrent_streams(m->data->multi)) {
infof(m->data, "client side MAX_CONCURRENT_STREAMS reached"
", skip (%u)", CONN_ATTACHED(conn));
return FALSE;
}
if(CONN_ATTACHED(conn) >=
Curl_conn_get_max_concurrent(m->data, conn, FIRSTSOCKET)) {
infof(m->data, "MAX_CONCURRENT_STREAMS reached, skip (%u)",
CONN_ATTACHED(conn));
return FALSE;
}
infof(m->data, "Multiplexed connection found");
}
return TRUE;
}
static bool url_match_ssl_use(struct connectdata *conn,
struct url_conn_match *m)
{
if(m->needle->handler->flags & PROTOPT_SSL) {
if(!Curl_conn_is_ssl(conn, FIRSTSOCKET))
return FALSE;
}
else if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
if(!(m->needle->handler->flags & PROTOPT_SSL_REUSE) ||
(get_protocol_family(conn->handler) != m->needle->handler->protocol))
return FALSE;
}
return TRUE;
}
#ifndef CURL_DISABLE_PROXY
static bool url_match_proxy_use(struct connectdata *conn,
struct url_conn_match *m)
{
if(m->needle->bits.httpproxy != conn->bits.httpproxy ||
m->needle->bits.socksproxy != conn->bits.socksproxy)
return FALSE;
if(m->needle->bits.socksproxy &&
!socks_proxy_info_matches(&m->needle->socks_proxy,
&conn->socks_proxy))
return FALSE;
if(m->needle->bits.httpproxy) {
if(m->needle->bits.tunnel_proxy != conn->bits.tunnel_proxy)
return FALSE;
if(!proxy_info_matches(&m->needle->http_proxy, &conn->http_proxy))
return FALSE;
if(IS_HTTPS_PROXY(m->needle->http_proxy.proxytype)) {
if(m->needle->http_proxy.proxytype != conn->http_proxy.proxytype)
return FALSE;
if(!Curl_ssl_conn_config_match(m->data, conn, TRUE)) {
DEBUGF(infof(m->data,
"Connection #%" FMT_OFF_T
" has different SSL proxy parameters, cannot reuse",
conn->connection_id));
return FALSE;
}
}
}
return TRUE;
}
#else
#define url_match_proxy_use(c,m) ((void)c, (void)m, TRUE)
#endif
#ifndef CURL_DISABLE_HTTP
static bool url_match_http_multiplex(struct connectdata *conn,
struct url_conn_match *m)
{
if(m->may_multiplex &&
(m->data->state.http_neg.allowed & (CURL_HTTP_V2x|CURL_HTTP_V3x)) &&
(m->needle->handler->protocol & CURLPROTO_HTTP) &&
!conn->httpversion_seen) {
if(m->data->set.pipewait) {
infof(m->data, "Server upgrade does not support multiplex yet, wait");
m->found = NULL;
m->wait_pipe = TRUE;
return TRUE;
}
infof(m->data, "Server upgrade cannot be used");
return FALSE;
}
return TRUE;
}
static bool url_match_http_version(struct connectdata *conn,
struct url_conn_match *m)
{
if((m->needle->handler->protocol & PROTO_FAMILY_HTTP)) {
switch(Curl_conn_http_version(m->data, conn)) {
case 30:
if(!(m->data->state.http_neg.allowed & CURL_HTTP_V3x)) {
DEBUGF(infof(m->data, "not reusing conn #%" CURL_FORMAT_CURL_OFF_T
", we do not want h3", conn->connection_id));
return FALSE;
}
break;
case 20:
if(!(m->data->state.http_neg.allowed & CURL_HTTP_V2x)) {
DEBUGF(infof(m->data, "not reusing conn #%" CURL_FORMAT_CURL_OFF_T
", we do not want h2", conn->connection_id));
return FALSE;
}
break;
default:
if(!(m->data->state.http_neg.allowed & CURL_HTTP_V1x)) {
DEBUGF(infof(m->data, "not reusing conn #%" CURL_FORMAT_CURL_OFF_T
", we do not want h1", conn->connection_id));
return FALSE;
}
break;
}
}
return TRUE;
}
#else
#define url_match_http_multiplex(c,m) ((void)c, (void)m, TRUE)
#define url_match_http_version(c,m) ((void)c, (void)m, TRUE)
#endif
static bool url_match_proto_config(struct connectdata *conn,
struct url_conn_match *m)
{
if(!url_match_http_version(conn, m))
return FALSE;
#ifdef USE_SSH
if(get_protocol_family(m->needle->handler) & PROTO_FAMILY_SSH) {
if(!ssh_config_matches(m->needle, conn))
return FALSE;
}
#endif
#ifndef CURL_DISABLE_FTP
else if(get_protocol_family(m->needle->handler) & PROTO_FAMILY_FTP) {
if(!ftp_conns_match(m->needle, conn))
return FALSE;
}
#endif
return TRUE;
}
static bool url_match_auth(struct connectdata *conn,
struct url_conn_match *m)
{
if(!(m->needle->handler->flags & PROTOPT_CREDSPERREQUEST)) {
if(Curl_timestrcmp(m->needle->user, conn->user) ||
Curl_timestrcmp(m->needle->passwd, conn->passwd) ||
Curl_timestrcmp(m->needle->sasl_authzid, conn->sasl_authzid) ||
Curl_timestrcmp(m->needle->oauth_bearer, conn->oauth_bearer)) {
return FALSE;
}
}
#ifdef HAVE_GSSAPI
if(m->needle->gssapi_delegation != conn->gssapi_delegation)
return FALSE;
#endif
return TRUE;
}
static bool url_match_destination(struct connectdata *conn,
struct url_conn_match *m)
{
if((m->needle->handler->flags&PROTOPT_SSL)
#ifndef CURL_DISABLE_PROXY
|| !m->needle->bits.httpproxy || m->needle->bits.tunnel_proxy
#endif
) {
if(!curl_strequal(m->needle->handler->scheme, conn->handler->scheme)) {
if(get_protocol_family(conn->handler) != m->needle->handler->protocol) {
return FALSE;
}
if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
DEBUGF(infof(m->data,
"Connection #%" FMT_OFF_T " has compatible protocol family, "
"but no SSL, no match", conn->connection_id));
return FALSE;
}
}
if((m->needle->bits.conn_to_host && !curl_strequal(
m->needle->conn_to_host.name, conn->conn_to_host.name)) ||
(m->needle->bits.conn_to_port &&
m->needle->conn_to_port != conn->conn_to_port))
return FALSE;
if(!curl_strequal(m->needle->host.name, conn->host.name) ||
m->needle->remote_port != conn->remote_port)
return FALSE;
}
return TRUE;
}
static bool url_match_ssl_config(struct connectdata *conn,
struct url_conn_match *m)
{
if((m->needle->handler->flags & PROTOPT_SSL) &&
!Curl_ssl_conn_config_match(m->data, conn, FALSE)) {
DEBUGF(infof(m->data,
"Connection #%" FMT_OFF_T
" has different SSL parameters, cannot reuse",
conn->connection_id));
return FALSE;
}
return TRUE;
}
#ifdef USE_NTLM
static bool url_match_auth_ntlm(struct connectdata *conn,
struct url_conn_match *m)
{
if(m->want_ntlm_http) {
if(Curl_timestrcmp(m->needle->user, conn->user) ||
Curl_timestrcmp(m->needle->passwd, conn->passwd)) {
if(conn->http_ntlm_state == NTLMSTATE_NONE)
m->found = conn;
return FALSE;
}
}
else if(conn->http_ntlm_state != NTLMSTATE_NONE) {
return FALSE;
}
#ifndef CURL_DISABLE_PROXY
if(m->want_proxy_ntlm_http) {
if(!conn->http_proxy.user || !conn->http_proxy.passwd)
return FALSE;
if(Curl_timestrcmp(m->needle->http_proxy.user,
conn->http_proxy.user) ||
Curl_timestrcmp(m->needle->http_proxy.passwd,
conn->http_proxy.passwd))
return FALSE;
}
else if(conn->proxy_ntlm_state != NTLMSTATE_NONE) {
return FALSE;
}
#endif
if(m->want_ntlm_http || m->want_proxy_ntlm_http) {
m->found = conn;
if((m->want_ntlm_http &&
(conn->http_ntlm_state != NTLMSTATE_NONE)) ||
(m->want_proxy_ntlm_http &&
(conn->proxy_ntlm_state != NTLMSTATE_NONE))) {
m->force_reuse = TRUE;
return TRUE;
}
return FALSE;
}
return TRUE;
}
#else
#define url_match_auth_ntlm(c,m) ((void)c, (void)m, TRUE)
#endif
static bool url_match_conn(struct connectdata *conn, void *userdata)
{
struct url_conn_match *m = userdata;
if(!url_match_connect_config(conn, m))
return FALSE;
if(!url_match_destination(conn, m))
return FALSE;
if(!url_match_fully_connected(conn, m))
return FALSE;
if(!url_match_multiplex_needs(conn, m))
return FALSE;
if(!url_match_ssl_use(conn, m))
return FALSE;
if(!url_match_proxy_use(conn, m))
return FALSE;
if(!url_match_ssl_config(conn, m))
return FALSE;
if(!url_match_http_multiplex(conn, m))
return FALSE;
else if(m->wait_pipe)
return TRUE;
if(!url_match_auth(conn, m))
return FALSE;
if(!url_match_proto_config(conn, m))
return FALSE;
if(!url_match_auth_ntlm(conn, m))
return FALSE;
else if(m->force_reuse)
return TRUE;
if(!url_match_multiplex_limits(conn, m))
return FALSE;
if(!CONN_INUSE(conn) && Curl_conn_seems_dead(conn, m->data, NULL)) {
Curl_conn_terminate(m->data, conn, FALSE);
return FALSE;
}
m->found = conn;
return TRUE;
}
static bool url_match_result(bool result, void *userdata)
{
struct url_conn_match *match = userdata;
(void)result;
if(match->found) {
Curl_attach_connection(match->data, match->found);
return TRUE;
}
else if(match->seen_single_use_conn && !match->seen_multiplex_conn) {
match->wait_pipe = FALSE;
}
else if(match->seen_pending_conn && match->data->set.pipewait) {
infof(match->data,
"Found pending candidate for reuse and CURLOPT_PIPEWAIT is set");
match->wait_pipe = TRUE;
}
match->force_reuse = FALSE;
return FALSE;
}
static bool
ConnectionExists(struct Curl_easy *data,
struct connectdata *needle,
struct connectdata **usethis,
bool *force_reuse,
bool *waitpipe)
{
struct url_conn_match match;
bool result;
memset(&match, 0, sizeof(match));
match.data = data;
match.needle = needle;
match.may_multiplex = xfer_may_multiplex(data, needle);
#ifdef USE_NTLM
match.want_ntlm_http = ((data->state.authhost.want & CURLAUTH_NTLM) &&
(needle->handler->protocol & PROTO_FAMILY_HTTP));
#ifndef CURL_DISABLE_PROXY
match.want_proxy_ntlm_http =
(needle->bits.proxy_user_passwd &&
(data->state.authproxy.want & CURLAUTH_NTLM) &&
(needle->handler->protocol & PROTO_FAMILY_HTTP));
#endif
#endif
result = Curl_cpool_find(data, needle->destination,
url_match_conn, url_match_result, &match);
*usethis = match.found;
*force_reuse = match.force_reuse;
*waitpipe = match.wait_pipe;
return result;
}
static struct connectdata *allocate_conn(struct Curl_easy *data)
{
struct connectdata *conn = calloc(1, sizeof(struct connectdata));
if(!conn)
return NULL;
conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD;
conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
conn->recv_idx = 0;
conn->send_idx = 0;
conn->connection_id = -1;
conn->remote_port = -1;
connclose(conn, "Default to force-close");
conn->created = curlx_now();
conn->keepalive = conn->created;
#ifndef CURL_DISABLE_PROXY
conn->http_proxy.proxytype = data->set.proxytype;
conn->socks_proxy.proxytype = CURLPROXY_SOCKS4;
conn->bits.proxy = (data->set.str[STRING_PROXY] &&
*data->set.str[STRING_PROXY]);
conn->bits.httpproxy = (conn->bits.proxy &&
(conn->http_proxy.proxytype == CURLPROXY_HTTP ||
conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0 ||
IS_HTTPS_PROXY(conn->http_proxy.proxytype)));
conn->bits.socksproxy = (conn->bits.proxy && !conn->bits.httpproxy);
if(data->set.str[STRING_PRE_PROXY] && *data->set.str[STRING_PRE_PROXY]) {
conn->bits.proxy = TRUE;
conn->bits.socksproxy = TRUE;
}
conn->bits.proxy_user_passwd = !!data->state.aptr.proxyuser;
conn->bits.tunnel_proxy = data->set.tunnel_thru_httpproxy;
#endif
#ifndef CURL_DISABLE_FTP
conn->bits.ftp_use_epsv = data->set.ftp_use_epsv;
conn->bits.ftp_use_eprt = data->set.ftp_use_eprt;
#endif
conn->ip_version = data->set.ipver;
conn->connect_only = data->set.connect_only;
conn->transport_wanted = TRNSPRT_TCP;
Curl_uint_spbset_init(&conn->xfers_attached);
if(data->set.str[STRING_DEVICE]) {
conn->localdev = strdup(data->set.str[STRING_DEVICE]);
if(!conn->localdev)
goto error;
}
#ifndef CURL_DISABLE_BINDLOCAL
conn->localportrange = data->set.localportrange;
conn->localport = data->set.localport;
#endif
conn->fclosesocket = data->set.fclosesocket;
conn->closesocket_client = data->set.closesocket_client;
conn->lastused = conn->created;
#ifdef HAVE_GSSAPI
conn->gssapi_delegation = data->set.gssapi_delegation;
#endif
return conn;
error:
free(conn->localdev);
free(conn);
return NULL;
}
const struct Curl_handler *Curl_get_scheme_handler(const char *scheme)
{
return Curl_getn_scheme_handler(scheme, strlen(scheme));
}
const struct Curl_handler *Curl_getn_scheme_handler(const char *scheme,
size_t len)
{
static const struct Curl_handler * const protocols[67] = {
#ifndef CURL_DISABLE_FILE
&Curl_handler_file,
#else
NULL,
#endif
NULL, NULL,
#if defined(USE_SSL) && !defined(CURL_DISABLE_GOPHER)
&Curl_handler_gophers,
#else
NULL,
#endif
NULL,
#ifdef USE_LIBRTMP
&Curl_handler_rtmpe,
#else
NULL,
#endif
#ifndef CURL_DISABLE_SMTP
&Curl_handler_smtp,
#else
NULL,
#endif
#ifdef USE_SSH
&Curl_handler_sftp,
#else
NULL,
#endif
#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) && \
(SIZEOF_CURL_OFF_T > 4)
&Curl_handler_smb,
#else
NULL,
#endif
#if defined(USE_SSL) && !defined(CURL_DISABLE_SMTP)
&Curl_handler_smtps,
#else
NULL,
#endif
#ifndef CURL_DISABLE_TELNET
&Curl_handler_telnet,
#else
NULL,
#endif
#ifndef CURL_DISABLE_GOPHER
&Curl_handler_gopher,
#else
NULL,
#endif
#ifndef CURL_DISABLE_TFTP
&Curl_handler_tftp,
#else
NULL,
#endif
NULL, NULL, NULL,
#if defined(USE_SSL) && !defined(CURL_DISABLE_FTP)
&Curl_handler_ftps,
#else
NULL,
#endif
#ifndef CURL_DISABLE_HTTP
&Curl_handler_http,
#else
NULL,
#endif
#ifndef CURL_DISABLE_IMAP
&Curl_handler_imap,
#else
NULL,
#endif
#ifdef USE_LIBRTMP
&Curl_handler_rtmps,
#else
NULL,
#endif
#ifdef USE_LIBRTMP
&Curl_handler_rtmpt,
#else
NULL,
#endif
NULL, NULL, NULL,
#if !defined(CURL_DISABLE_LDAP) && \
!defined(CURL_DISABLE_LDAPS) && \
((defined(USE_OPENLDAP) && defined(USE_SSL)) || \
(!defined(USE_OPENLDAP) && defined(HAVE_LDAP_SSL)))
&Curl_handler_ldaps,
#else
NULL,
#endif
#if !defined(CURL_DISABLE_WEBSOCKETS) && \
defined(USE_SSL) && !defined(CURL_DISABLE_HTTP)
&Curl_handler_wss,
#else
NULL,
#endif
#if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP)
&Curl_handler_https,
#else
NULL,
#endif
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
#ifndef CURL_DISABLE_RTSP
&Curl_handler_rtsp,
#else
NULL,
#endif
#if defined(USE_SSL) && !defined(CURL_DISABLE_SMB) && \
defined(USE_CURL_NTLM_CORE) && (SIZEOF_CURL_OFF_T > 4)
&Curl_handler_smbs,
#else
NULL,
#endif
#if defined(USE_SSH)
&Curl_handler_scp,
#else
NULL,
#endif
NULL, NULL, NULL,
#ifndef CURL_DISABLE_POP3
&Curl_handler_pop3,
#else
NULL,
#endif
NULL, NULL,
#ifdef USE_LIBRTMP
&Curl_handler_rtmp,
#else
NULL,
#endif
NULL, NULL, NULL,
#ifdef USE_LIBRTMP
&Curl_handler_rtmpte,
#else
NULL,
#endif
NULL, NULL, NULL,
#ifndef CURL_DISABLE_DICT
&Curl_handler_dict,
#else
NULL,
#endif
NULL, NULL, NULL,
#ifndef CURL_DISABLE_MQTT
&Curl_handler_mqtt,
#else
NULL,
#endif
#if defined(USE_SSL) && !defined(CURL_DISABLE_POP3)
&Curl_handler_pop3s,
#else
NULL,
#endif
#if defined(USE_SSL) && !defined(CURL_DISABLE_IMAP)
&Curl_handler_imaps,
#else
NULL,
#endif
NULL,
#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP)
&Curl_handler_ws,
#else
NULL,
#endif
NULL,
#ifdef USE_LIBRTMP
&Curl_handler_rtmpts,
#else
NULL,
#endif
#ifndef CURL_DISABLE_LDAP
&Curl_handler_ldap,
#else
NULL,
#endif
NULL, NULL,
#ifndef CURL_DISABLE_FTP
&Curl_handler_ftp,
#else
NULL,
#endif
};
if(len && (len <= 7)) {
const char *s = scheme;
size_t l = len;
const struct Curl_handler *h;
unsigned int c = 978;
while(l) {
c <<= 5;
c += (unsigned int)Curl_raw_tolower(*s);
s++;
l--;
}
h = protocols[c % 67];
if(h && curl_strnequal(scheme, h->scheme, len) && !h->scheme[len])
return h;
}
return NULL;
}
static CURLcode findprotocol(struct Curl_easy *data,
struct connectdata *conn,
const char *protostr)
{
const struct Curl_handler *p = Curl_get_scheme_handler(protostr);
if(p &&
(data->set.allowed_protocols & p->protocol)) {
if(data->state.this_is_a_follow &&
!(data->set.redir_protocols & p->protocol))
;
else {
conn->handler = conn->given = p;
return CURLE_OK;
}
}
failf(data, "Protocol \"%s\" %s%s", protostr,
p ? "disabled" : "not supported",
data->state.this_is_a_follow ? " (in redirect)":"");
return CURLE_UNSUPPORTED_PROTOCOL;
}
CURLcode Curl_uc_to_curlcode(CURLUcode uc)
{
switch(uc) {
default:
return CURLE_URL_MALFORMAT;
case CURLUE_UNSUPPORTED_SCHEME:
return CURLE_UNSUPPORTED_PROTOCOL;
case CURLUE_OUT_OF_MEMORY:
return CURLE_OUT_OF_MEMORY;
case CURLUE_USER_NOT_ALLOWED:
return CURLE_LOGIN_DENIED;
}
}
#ifdef USE_IPV6
static void zonefrom_url(CURLU *uh, struct Curl_easy *data,
struct connectdata *conn)
{
char *zoneid;
CURLUcode uc = curl_url_get(uh, CURLUPART_ZONEID, &zoneid, 0);
#ifdef CURL_DISABLE_VERBOSE_STRINGS
(void)data;
#endif
if(!uc && zoneid) {
const char *p = zoneid;
curl_off_t scope;
if(!curlx_str_number(&p, &scope, UINT_MAX))
conn->scope_id = (unsigned int)scope;
#ifdef HAVE_IF_NAMETOINDEX
else {
#elif defined(_WIN32)
else if(Curl_if_nametoindex) {
#endif
#if defined(HAVE_IF_NAMETOINDEX) || defined(_WIN32)
unsigned int scopeidx = 0;
#ifdef HAVE_IF_NAMETOINDEX
scopeidx = if_nametoindex(zoneid);
#else
scopeidx = Curl_if_nametoindex(zoneid);
#endif
if(!scopeidx) {
#ifndef CURL_DISABLE_VERBOSE_STRINGS
char buffer[STRERROR_LEN];
infof(data, "Invalid zoneid: %s; %s", zoneid,
curlx_strerror(errno, buffer, sizeof(buffer)));
#endif
}
else
conn->scope_id = scopeidx;
}
#endif
free(zoneid);
}
}
#else
#define zonefrom_url(a,b,c) Curl_nop_stmt
#endif
static CURLcode parseurlandfillconn(struct Curl_easy *data,
struct connectdata *conn)
{
CURLcode result;
CURLU *uh;
CURLUcode uc;
char *hostname;
bool use_set_uh = (data->set.uh && !data->state.this_is_a_follow);
up_free(data);
if(use_set_uh) {
uh = data->state.uh = curl_url_dup(data->set.uh);
}
else {
uh = data->state.uh = curl_url();
}
if(!uh)
return CURLE_OUT_OF_MEMORY;
if(data->set.str[STRING_DEFAULT_PROTOCOL] &&
!Curl_is_absolute_url(data->state.url, NULL, 0, TRUE)) {
char *url = curl_maprintf("%s://%s",
data->set.str[STRING_DEFAULT_PROTOCOL],
data->state.url);
if(!url)
return CURLE_OUT_OF_MEMORY;
if(data->state.url_alloc)
free(data->state.url);
data->state.url = url;
data->state.url_alloc = TRUE;
}
if(!use_set_uh) {
char *newurl;
uc = curl_url_set(uh, CURLUPART_URL, data->state.url, (unsigned int)
(CURLU_GUESS_SCHEME |
CURLU_NON_SUPPORT_SCHEME |
(data->set.disallow_username_in_url ?
CURLU_DISALLOW_USER : 0) |
(data->set.path_as_is ? CURLU_PATH_AS_IS : 0)));
if(uc) {
failf(data, "URL rejected: %s", curl_url_strerror(uc));
return Curl_uc_to_curlcode(uc);
}
uc = curl_url_get(uh, CURLUPART_URL, &newurl, 0);
if(uc)
return Curl_uc_to_curlcode(uc);
if(data->state.url_alloc)
free(data->state.url);
data->state.url = newurl;
data->state.url_alloc = TRUE;
}
uc = curl_url_get(uh, CURLUPART_SCHEME, &data->state.up.scheme, 0);
if(uc)
return Curl_uc_to_curlcode(uc);
uc = curl_url_get(uh, CURLUPART_HOST, &data->state.up.hostname, 0);
if(uc) {
if(!curl_strequal("file", data->state.up.scheme))
return CURLE_OUT_OF_MEMORY;
}
else if(strlen(data->state.up.hostname) > MAX_URL_LEN) {
failf(data, "Too long hostname (maximum is %d)", MAX_URL_LEN);
return CURLE_URL_MALFORMAT;
}
hostname = data->state.up.hostname;
if(hostname && hostname[0] == '[') {
size_t hlen;
conn->bits.ipv6_ip = TRUE;
hostname++;
hlen = strlen(hostname);
hostname[hlen - 1] = 0;
zonefrom_url(uh, data, conn);
}
conn->host.rawalloc = strdup(hostname ? hostname : "");
if(!conn->host.rawalloc)
return CURLE_OUT_OF_MEMORY;
conn->host.name = conn->host.rawalloc;
result = Curl_idnconvert_hostname(&conn->host);
if(result)
return result;
#ifndef CURL_DISABLE_HSTS
if(data->hsts && curl_strequal("http", data->state.up.scheme)) {
if(Curl_hsts(data->hsts, conn->host.name, strlen(conn->host.name), TRUE)) {
char *url;
Curl_safefree(data->state.up.scheme);
uc = curl_url_set(uh, CURLUPART_SCHEME, "https", 0);
if(uc)
return Curl_uc_to_curlcode(uc);
if(data->state.url_alloc)
Curl_safefree(data->state.url);
uc = curl_url_get(uh, CURLUPART_URL, &url, 0);
if(uc)
return Curl_uc_to_curlcode(uc);
uc = curl_url_get(uh, CURLUPART_SCHEME, &data->state.up.scheme, 0);
if(uc) {
free(url);
return Curl_uc_to_curlcode(uc);
}
data->state.url = url;
data->state.url_alloc = TRUE;
infof(data, "Switched from HTTP to HTTPS due to HSTS => %s",
data->state.url);
}
}
#endif
result = findprotocol(data, conn, data->state.up.scheme);
if(result)
return result;
if(!data->state.aptr.passwd || (data->state.creds_from != CREDS_OPTION)) {
uc = curl_url_get(uh, CURLUPART_PASSWORD, &data->state.up.password, 0);
if(!uc) {
char *decoded;
result = Curl_urldecode(data->state.up.password, 0, &decoded, NULL,
conn->handler->flags&PROTOPT_USERPWDCTRL ?
REJECT_ZERO : REJECT_CTRL);
if(result)
return result;
conn->passwd = decoded;
result = Curl_setstropt(&data->state.aptr.passwd, decoded);
if(result)
return result;
data->state.creds_from = CREDS_URL;
}
else if(uc != CURLUE_NO_PASSWORD)
return Curl_uc_to_curlcode(uc);
}
if(!data->state.aptr.user || (data->state.creds_from != CREDS_OPTION)) {
uc = curl_url_get(uh, CURLUPART_USER, &data->state.up.user, 0);
if(!uc) {
char *decoded;
result = Curl_urldecode(data->state.up.user, 0, &decoded, NULL,
conn->handler->flags&PROTOPT_USERPWDCTRL ?
REJECT_ZERO : REJECT_CTRL);
if(result)
return result;
conn->user = decoded;
result = Curl_setstropt(&data->state.aptr.user, decoded);
data->state.creds_from = CREDS_URL;
}
else if(uc != CURLUE_NO_USER)
return Curl_uc_to_curlcode(uc);
if(result)
return result;
}
uc = curl_url_get(uh, CURLUPART_OPTIONS, &data->state.up.options,
CURLU_URLDECODE);
if(!uc) {
conn->options = strdup(data->state.up.options);
if(!conn->options)
return CURLE_OUT_OF_MEMORY;
}
else if(uc != CURLUE_NO_OPTIONS)
return Curl_uc_to_curlcode(uc);
uc = curl_url_get(uh, CURLUPART_PATH, &data->state.up.path,
CURLU_URLENCODE);
if(uc)
return Curl_uc_to_curlcode(uc);
uc = curl_url_get(uh, CURLUPART_PORT, &data->state.up.port,
CURLU_DEFAULT_PORT);
if(uc) {
if(!curl_strequal("file", data->state.up.scheme))
return CURLE_OUT_OF_MEMORY;
}
else {
curl_off_t port;
bool valid = TRUE;
if(data->set.use_port && data->state.allow_port)
port = data->set.use_port;
else {
const char *p = data->state.up.port;
if(curlx_str_number(&p, &port, 0xffff))
valid = FALSE;
}
if(valid)
conn->remote_port = (unsigned short)port;
}
(void)curl_url_get(uh, CURLUPART_QUERY, &data->state.up.query, 0);
#ifdef USE_IPV6
if(data->set.scope_id)
conn->scope_id = data->set.scope_id;
#endif
return CURLE_OK;
}
static CURLcode setup_range(struct Curl_easy *data)
{
struct UrlState *s = &data->state;
s->resume_from = data->set.set_resume_from;
if(s->resume_from || data->set.str[STRING_SET_RANGE]) {
if(s->rangestringalloc)
free(s->range);
if(s->resume_from)
s->range = curl_maprintf("%" FMT_OFF_T "-", s->resume_from);
else
s->range = strdup(data->set.str[STRING_SET_RANGE]);
if(!s->range)
return CURLE_OUT_OF_MEMORY;
s->rangestringalloc = TRUE;
s->use_range = TRUE;
}
else
s->use_range = FALSE;
return CURLE_OK;
}
static CURLcode setup_connection_internals(struct Curl_easy *data,
struct connectdata *conn)
{
const char *hostname;
int port;
CURLcode result;
if(conn->handler->setup_connection) {
result = conn->handler->setup_connection(data, conn);
if(result)
return result;
}
#ifndef CURL_DISABLE_PROXY
if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
hostname = conn->http_proxy.host.name;
port = conn->http_proxy.port;
}
else
#endif
{
port = conn->remote_port;
if(conn->bits.conn_to_host)
hostname = conn->conn_to_host.name;
else
hostname = conn->host.name;
}
#ifdef USE_IPV6
conn->destination = curl_maprintf("%u/%d/%s", conn->scope_id, port,
hostname);
#else
conn->destination = curl_maprintf("%d/%s", port, hostname);
#endif
if(!conn->destination)
return CURLE_OUT_OF_MEMORY;
Curl_strntolower(conn->destination, conn->destination,
strlen(conn->destination));
return CURLE_OK;
}
#ifndef CURL_DISABLE_PROXY
#ifndef CURL_DISABLE_HTTP
static char *detect_proxy(struct Curl_easy *data,
struct connectdata *conn)
{
char *proxy = NULL;
char proxy_env[20];
const char *envp = proxy_env;
#ifdef CURL_DISABLE_VERBOSE_STRINGS
(void)data;
#endif
curl_msnprintf(proxy_env, sizeof(proxy_env), "%s_proxy",
conn->handler->scheme);
proxy = curl_getenv(proxy_env);
if(!proxy && !curl_strequal("http_proxy", proxy_env)) {
Curl_strntoupper(proxy_env, proxy_env, sizeof(proxy_env));
proxy = curl_getenv(proxy_env);
}
if(!proxy) {
#ifndef CURL_DISABLE_WEBSOCKETS
if(curl_strequal("ws_proxy", proxy_env)) {
proxy = curl_getenv("http_proxy");
}
else if(curl_strequal("wss_proxy", proxy_env)) {
proxy = curl_getenv("https_proxy");
if(!proxy)
proxy = curl_getenv("HTTPS_PROXY");
}
if(!proxy) {
#endif
envp = "all_proxy";
proxy = curl_getenv(envp);
if(!proxy) {
envp = "ALL_PROXY";
proxy = curl_getenv(envp);
}
#ifndef CURL_DISABLE_WEBSOCKETS
}
#endif
}
if(proxy)
infof(data, "Uses proxy env variable %s == '%s'", envp, proxy);
return proxy;
}
#endif
static CURLcode parse_proxy(struct Curl_easy *data,
struct connectdata *conn, char *proxy,
long proxytype)
{
char *portptr = NULL;
int port = -1;
char *proxyuser = NULL;
char *proxypasswd = NULL;
char *host = NULL;
bool sockstype;
CURLUcode uc;
struct proxy_info *proxyinfo;
CURLU *uhp = curl_url();
CURLcode result = CURLE_OK;
char *scheme = NULL;
#ifdef USE_UNIX_SOCKETS
char *path = NULL;
bool is_unix_proxy = FALSE;
#endif
if(!uhp) {
result = CURLE_OUT_OF_MEMORY;
goto error;
}
uc = curl_url_set(uhp, CURLUPART_URL, proxy,
CURLU_NON_SUPPORT_SCHEME|CURLU_GUESS_SCHEME);
if(!uc) {
uc = curl_url_get(uhp, CURLUPART_SCHEME, &scheme, 0);
if(uc) {
result = CURLE_OUT_OF_MEMORY;
goto error;
}
if(curl_strequal("https", scheme)) {
if(proxytype != CURLPROXY_HTTPS2)
proxytype = CURLPROXY_HTTPS;
else
proxytype = CURLPROXY_HTTPS2;
}
else if(curl_strequal("socks5h", scheme))
proxytype = CURLPROXY_SOCKS5_HOSTNAME;
else if(curl_strequal("socks5", scheme))
proxytype = CURLPROXY_SOCKS5;
else if(curl_strequal("socks4a", scheme))
proxytype = CURLPROXY_SOCKS4A;
else if(curl_strequal("socks4", scheme) ||
curl_strequal("socks", scheme))
proxytype = CURLPROXY_SOCKS4;
else if(curl_strequal("http", scheme))
;
else {
failf(data, "Unsupported proxy scheme for \'%s\'", proxy);
result = CURLE_COULDNT_CONNECT;
goto error;
}
}
else {
failf(data, "Unsupported proxy syntax in \'%s\': %s", proxy,
curl_url_strerror(uc));
result = CURLE_COULDNT_RESOLVE_PROXY;
goto error;
}
#ifdef USE_SSL
if(!Curl_ssl_supports(data, SSLSUPP_HTTPS_PROXY))
#endif
if(IS_HTTPS_PROXY(proxytype)) {
failf(data, "Unsupported proxy \'%s\', libcurl is built without the "
"HTTPS-proxy support.", proxy);
result = CURLE_NOT_BUILT_IN;
goto error;
}
sockstype =
proxytype == CURLPROXY_SOCKS5_HOSTNAME ||
proxytype == CURLPROXY_SOCKS5 ||
proxytype == CURLPROXY_SOCKS4A ||
proxytype == CURLPROXY_SOCKS4;
proxyinfo = sockstype ? &conn->socks_proxy : &conn->http_proxy;
proxyinfo->proxytype = (unsigned char)proxytype;
uc = curl_url_get(uhp, CURLUPART_USER, &proxyuser, CURLU_URLDECODE);
if(uc && (uc != CURLUE_NO_USER))
goto error;
uc = curl_url_get(uhp, CURLUPART_PASSWORD, &proxypasswd, CURLU_URLDECODE);
if(uc && (uc != CURLUE_NO_PASSWORD))
goto error;
if(proxyuser || proxypasswd) {
free(proxyinfo->user);
proxyinfo->user = proxyuser;
result = Curl_setstropt(&data->state.aptr.proxyuser, proxyuser);
proxyuser = NULL;
if(result)
goto error;
Curl_safefree(proxyinfo->passwd);
if(!proxypasswd) {
proxypasswd = strdup("");
if(!proxypasswd) {
result = CURLE_OUT_OF_MEMORY;
goto error;
}
}
proxyinfo->passwd = proxypasswd;
result = Curl_setstropt(&data->state.aptr.proxypasswd, proxypasswd);
proxypasswd = NULL;
if(result)
goto error;
conn->bits.proxy_user_passwd = TRUE;
}
(void)curl_url_get(uhp, CURLUPART_PORT, &portptr, 0);
if(portptr) {
curl_off_t num;
const char *p = portptr;
if(!curlx_str_number(&p, &num, 0xffff))
port = (int)num;
free(portptr);
}
else {
if(data->set.proxyport)
port = (int)data->set.proxyport;
else {
if(IS_HTTPS_PROXY(proxytype))
port = CURL_DEFAULT_HTTPS_PROXY_PORT;
else
port = CURL_DEFAULT_PROXY_PORT;
}
}
if(port >= 0)
proxyinfo->port = port;
uc = curl_url_get(uhp, CURLUPART_HOST, &host, CURLU_URLDECODE);
if(uc) {
result = CURLE_OUT_OF_MEMORY;
goto error;
}
#ifdef USE_UNIX_SOCKETS
if(sockstype && curl_strequal(UNIX_SOCKET_PREFIX, host)) {
uc = curl_url_get(uhp, CURLUPART_PATH, &path, CURLU_URLDECODE);
if(uc) {
result = CURLE_OUT_OF_MEMORY;
goto error;
}
if(strcmp("/", path)) {
is_unix_proxy = TRUE;
free(host);
host = curl_maprintf(UNIX_SOCKET_PREFIX"%s", path);
if(!host) {
result = CURLE_OUT_OF_MEMORY;
goto error;
}
free(proxyinfo->host.rawalloc);
proxyinfo->host.rawalloc = host;
proxyinfo->host.name = host;
host = NULL;
}
}
if(!is_unix_proxy) {
#endif
free(proxyinfo->host.rawalloc);
proxyinfo->host.rawalloc = host;
if(host[0] == '[') {
size_t len = strlen(host);
host[len-1] = 0;
host++;
zonefrom_url(uhp, data, conn);
}
proxyinfo->host.name = host;
host = NULL;
#ifdef USE_UNIX_SOCKETS
}
#endif
error:
free(proxyuser);
free(proxypasswd);
free(host);
free(scheme);
#ifdef USE_UNIX_SOCKETS
free(path);
#endif
curl_url_cleanup(uhp);
return result;
}
static CURLcode parse_proxy_auth(struct Curl_easy *data,
struct connectdata *conn)
{
const char *proxyuser = data->state.aptr.proxyuser ?
data->state.aptr.proxyuser : "";
const char *proxypasswd = data->state.aptr.proxypasswd ?
data->state.aptr.proxypasswd : "";
CURLcode result = CURLE_OUT_OF_MEMORY;
conn->http_proxy.user = strdup(proxyuser);
if(conn->http_proxy.user) {
conn->http_proxy.passwd = strdup(proxypasswd);
if(conn->http_proxy.passwd)
result = CURLE_OK;
else
Curl_safefree(conn->http_proxy.user);
}
return result;
}
static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data,
struct connectdata *conn)
{
char *proxy = NULL;
char *socksproxy = NULL;
char *no_proxy = NULL;
CURLcode result = CURLE_OK;
if(conn->bits.proxy_user_passwd) {
result = parse_proxy_auth(data, conn);
if(result)
goto out;
}
if(data->set.str[STRING_PROXY]) {
proxy = strdup(data->set.str[STRING_PROXY]);
if(!proxy) {
failf(data, "memory shortage");
result = CURLE_OUT_OF_MEMORY;
goto out;
}
}
if(data->set.str[STRING_PRE_PROXY]) {
socksproxy = strdup(data->set.str[STRING_PRE_PROXY]);
if(!socksproxy) {
failf(data, "memory shortage");
result = CURLE_OUT_OF_MEMORY;
goto out;
}
}
if(!data->set.str[STRING_NOPROXY]) {
const char *p = "no_proxy";
no_proxy = curl_getenv(p);
if(!no_proxy) {
p = "NO_PROXY";
no_proxy = curl_getenv(p);
}
if(no_proxy) {
infof(data, "Uses proxy env variable %s == '%s'", p, no_proxy);
}
}
if(Curl_check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY] ?
data->set.str[STRING_NOPROXY] : no_proxy)) {
Curl_safefree(proxy);
Curl_safefree(socksproxy);
}
#ifndef CURL_DISABLE_HTTP
else if(!proxy && !socksproxy)
proxy = detect_proxy(data, conn);
#endif
Curl_safefree(no_proxy);
#ifdef USE_UNIX_SOCKETS
if(proxy && conn->unix_domain_socket) {
free(proxy);
proxy = NULL;
}
#endif
if(proxy && (!*proxy || (conn->handler->flags & PROTOPT_NONETWORK))) {
free(proxy);
proxy = NULL;
}
if(socksproxy && (!*socksproxy ||
(conn->handler->flags & PROTOPT_NONETWORK))) {
free(socksproxy);
socksproxy = NULL;
}
if(proxy || socksproxy) {
long ptype = conn->http_proxy.proxytype;
if(proxy) {
result = parse_proxy(data, conn, proxy, ptype);
Curl_safefree(proxy);
if(result)
goto out;
}
if(socksproxy) {
result = parse_proxy(data, conn, socksproxy, ptype);
Curl_safefree(socksproxy);
if(result)
goto out;
}
if(conn->http_proxy.host.rawalloc) {
#ifdef CURL_DISABLE_HTTP
result = CURLE_UNSUPPORTED_PROTOCOL;
goto out;
#else
if(!(conn->handler->protocol & PROTO_FAMILY_HTTP)) {
if((conn->handler->flags & PROTOPT_PROXY_AS_HTTP) &&
!conn->bits.tunnel_proxy)
conn->handler = &Curl_handler_http;
else
conn->bits.tunnel_proxy = TRUE;
}
conn->bits.httpproxy = TRUE;
#endif
}
else {
conn->bits.httpproxy = FALSE;
conn->bits.tunnel_proxy = FALSE;
}
if(conn->socks_proxy.host.rawalloc) {
if(!conn->http_proxy.host.rawalloc) {
if(!conn->socks_proxy.user) {
conn->socks_proxy.user = conn->http_proxy.user;
conn->http_proxy.user = NULL;
free(conn->socks_proxy.passwd);
conn->socks_proxy.passwd = conn->http_proxy.passwd;
conn->http_proxy.passwd = NULL;
}
}
conn->bits.socksproxy = TRUE;
}
else
conn->bits.socksproxy = FALSE;
}
else {
conn->bits.socksproxy = FALSE;
conn->bits.httpproxy = FALSE;
}
conn->bits.proxy = conn->bits.httpproxy || conn->bits.socksproxy;
if(!conn->bits.proxy) {
conn->bits.proxy = FALSE;
conn->bits.httpproxy = FALSE;
conn->bits.socksproxy = FALSE;
conn->bits.proxy_user_passwd = FALSE;
conn->bits.tunnel_proxy = FALSE;
conn->http_proxy.proxytype = CURLPROXY_HTTP;
}
out:
free(socksproxy);
free(proxy);
return result;
}
#endif
CURLcode Curl_parse_login_details(const char *login, const size_t len,
char **userp, char **passwdp,
char **optionsp)
{
char *ubuf = NULL;
char *pbuf = NULL;
const char *psep = NULL;
const char *osep = NULL;
size_t ulen;
size_t plen;
size_t olen;
DEBUGASSERT(userp);
DEBUGASSERT(passwdp);
psep = memchr(login, ':', len);
if(optionsp)
osep = memchr(login, ';', len);
ulen = (psep ?
(size_t)(osep && psep > osep ? osep - login : psep - login) :
(osep ? (size_t)(osep - login) : len));
plen = (psep ?
(osep && osep > psep ? (size_t)(osep - psep) :
(size_t)(login + len - psep)) - 1 : 0);
olen = (osep ?
(psep && psep > osep ? (size_t)(psep - osep) :
(size_t)(login + len - osep)) - 1 : 0);
ubuf = Curl_memdup0(login, ulen);
if(!ubuf)
goto error;
if(psep) {
pbuf = Curl_memdup0(&psep[1], plen);
if(!pbuf)
goto error;
}
if(optionsp) {
char *obuf = NULL;
if(olen) {
obuf = Curl_memdup0(&osep[1], olen);
if(!obuf)
goto error;
}
*optionsp = obuf;
}
*userp = ubuf;
*passwdp = pbuf;
return CURLE_OK;
error:
free(ubuf);
free(pbuf);
return CURLE_OUT_OF_MEMORY;
}
static CURLcode parse_remote_port(struct Curl_easy *data,
struct connectdata *conn)
{
if(data->set.use_port && data->state.allow_port) {
char portbuf[16];
CURLUcode uc;
conn->remote_port = data->set.use_port;
curl_msnprintf(portbuf, sizeof(portbuf), "%d", conn->remote_port);
uc = curl_url_set(data->state.uh, CURLUPART_PORT, portbuf, 0);
if(uc)
return CURLE_OUT_OF_MEMORY;
}
return CURLE_OK;
}
#ifndef CURL_DISABLE_NETRC
static bool str_has_ctrl(const char *input)
{
if(input) {
const unsigned char *str = (const unsigned char *)input;
while(*str) {
if(*str < 0x20)
return TRUE;
str++;
}
}
return FALSE;
}
#endif
static CURLcode override_login(struct Curl_easy *data,
struct connectdata *conn)
{
CURLUcode uc;
char **userp = &conn->user;
char **passwdp = &conn->passwd;
char **optionsp = &conn->options;
if(data->set.str[STRING_OPTIONS]) {
free(*optionsp);
*optionsp = strdup(data->set.str[STRING_OPTIONS]);
if(!*optionsp)
return CURLE_OUT_OF_MEMORY;
}
#ifndef CURL_DISABLE_NETRC
if(data->set.use_netrc == CURL_NETRC_REQUIRED) {
Curl_safefree(*userp);
Curl_safefree(*passwdp);
}
conn->bits.netrc = FALSE;
if(data->set.use_netrc && !data->set.str[STRING_USERNAME]) {
bool url_provided = FALSE;
if(data->state.aptr.user &&
(data->state.creds_from != CREDS_NETRC)) {
userp = &data->state.aptr.user;
url_provided = TRUE;
}
if(!*passwdp) {
NETRCcode ret = Curl_parsenetrc(&data->state.netrc, conn->host.name,
userp, passwdp,
data->set.str[STRING_NETRC_FILE]);
if(ret && ((ret == NETRC_NO_MATCH) ||
(data->set.use_netrc == CURL_NETRC_OPTIONAL))) {
infof(data, "Couldn't find host %s in the %s file; using defaults",
conn->host.name,
(data->set.str[STRING_NETRC_FILE] ?
data->set.str[STRING_NETRC_FILE] : ".netrc"));
}
else if(ret) {
const char *m = Curl_netrc_strerror(ret);
failf(data, ".netrc error: %s", m);
return CURLE_READ_ERROR;
}
else {
if(!(conn->handler->flags&PROTOPT_USERPWDCTRL)) {
if(str_has_ctrl(*userp) || str_has_ctrl(*passwdp)) {
failf(data, "control code detected in .netrc credentials");
return CURLE_READ_ERROR;
}
}
conn->bits.netrc = TRUE;
}
}
if(url_provided) {
free(conn->user);
conn->user = strdup(*userp);
if(!conn->user)
return CURLE_OUT_OF_MEMORY;
}
if(!*userp && *passwdp) {
*userp = strdup("");
if(!*userp)
return CURLE_OUT_OF_MEMORY;
}
}
#endif
if(*userp) {
CURLcode result;
if(data->state.aptr.user != *userp) {
result = Curl_setstropt(&data->state.aptr.user, *userp);
if(result)
return result;
data->state.creds_from = CREDS_NETRC;
}
}
if(data->state.aptr.user) {
uc = curl_url_set(data->state.uh, CURLUPART_USER, data->state.aptr.user,
CURLU_URLENCODE);
if(uc)
return Curl_uc_to_curlcode(uc);
if(!*userp) {
*userp = strdup(data->state.aptr.user);
if(!*userp)
return CURLE_OUT_OF_MEMORY;
}
}
if(*passwdp) {
CURLcode result = Curl_setstropt(&data->state.aptr.passwd, *passwdp);
if(result)
return result;
data->state.creds_from = CREDS_NETRC;
}
if(data->state.aptr.passwd) {
uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD,
data->state.aptr.passwd, CURLU_URLENCODE);
if(uc)
return Curl_uc_to_curlcode(uc);
if(!*passwdp) {
*passwdp = strdup(data->state.aptr.passwd);
if(!*passwdp)
return CURLE_OUT_OF_MEMORY;
}
}
return CURLE_OK;
}
static CURLcode set_login(struct Curl_easy *data,
struct connectdata *conn)
{
CURLcode result = CURLE_OK;
const char *setuser = CURL_DEFAULT_USER;
const char *setpasswd = CURL_DEFAULT_PASSWORD;
if((conn->handler->flags & PROTOPT_NEEDSPWD) && !data->state.aptr.user)
;
else {
setuser = "";
setpasswd = "";
}
if(!conn->user) {
conn->user = strdup(setuser);
if(!conn->user)
return CURLE_OUT_OF_MEMORY;
}
if(!conn->passwd) {
conn->passwd = strdup(setpasswd);
if(!conn->passwd)
result = CURLE_OUT_OF_MEMORY;
}
return result;
}
static CURLcode parse_connect_to_host_port(struct Curl_easy *data,
const char *host,
char **hostname_result,
int *port_result)
{
char *host_dup;
char *hostptr;
char *host_portno;
char *portptr;
int port = -1;
CURLcode result = CURLE_OK;
#ifdef CURL_DISABLE_VERBOSE_STRINGS
(void)data;
#endif
*hostname_result = NULL;
*port_result = -1;
if(!host || !*host)
return CURLE_OK;
host_dup = strdup(host);
if(!host_dup)
return CURLE_OUT_OF_MEMORY;
hostptr = host_dup;
portptr = hostptr;
if(*hostptr == '[') {
#ifdef USE_IPV6
char *ptr = ++hostptr;
while(*ptr && (ISXDIGIT(*ptr) || (*ptr == ':') || (*ptr == '.')))
ptr++;
if(*ptr == '%') {
if(strncmp("%25", ptr, 3))
infof(data, "Please URL encode %% as %%25, see RFC 6874.");
ptr++;
while(*ptr && (ISALPHA(*ptr) || ISXDIGIT(*ptr) || (*ptr == '-') ||
(*ptr == '.') || (*ptr == '_') || (*ptr == '~')))
ptr++;
}
if(*ptr == ']')
*ptr++ = '\0';
else
infof(data, "Invalid IPv6 address format");
portptr = ptr;
#else
failf(data, "Use of IPv6 in *_CONNECT_TO without IPv6 support built-in");
result = CURLE_NOT_BUILT_IN;
goto error;
#endif
}
host_portno = strchr(portptr, ':');
if(host_portno) {
*host_portno = '\0';
host_portno++;
if(*host_portno) {
curl_off_t portparse;
const char *p = host_portno;
if(curlx_str_number(&p, &portparse, 0xffff)) {
failf(data, "No valid port number in connect to host string (%s)",
host_portno);
result = CURLE_SETOPT_OPTION_SYNTAX;
goto error;
}
port = (int)portparse;
}
}
DEBUGASSERT(hostptr);
*hostname_result = strdup(hostptr);
if(!*hostname_result) {
result = CURLE_OUT_OF_MEMORY;
goto error;
}
*port_result = port;
error:
free(host_dup);
return result;
}
static CURLcode parse_connect_to_string(struct Curl_easy *data,
struct connectdata *conn,
const char *conn_to_host,
char **host_result,
int *port_result)
{
CURLcode result = CURLE_OK;
const char *ptr = conn_to_host;
bool host_match = FALSE;
bool port_match = FALSE;
*host_result = NULL;
*port_result = -1;
if(*ptr == ':') {
host_match = TRUE;
ptr++;
}
else {
size_t hostname_to_match_len;
char *hostname_to_match = curl_maprintf("%s%s%s",
conn->bits.ipv6_ip ? "[" : "",
conn->host.name,
conn->bits.ipv6_ip ? "]" : "");
if(!hostname_to_match)
return CURLE_OUT_OF_MEMORY;
hostname_to_match_len = strlen(hostname_to_match);
host_match = curl_strnequal(ptr, hostname_to_match,
hostname_to_match_len);
free(hostname_to_match);
ptr += hostname_to_match_len;
host_match = host_match && *ptr == ':';
ptr++;
}
if(host_match) {
if(*ptr == ':') {
port_match = TRUE;
ptr++;
}
else {
char *ptr_next = strchr(ptr, ':');
if(ptr_next) {
curl_off_t port_to_match;
if(!curlx_str_number(&ptr, &port_to_match, 0xffff) &&
(port_to_match == (curl_off_t)conn->remote_port))
port_match = TRUE;
ptr = ptr_next + 1;
}
}
}
if(host_match && port_match) {
result = parse_connect_to_host_port(data, ptr, host_result, port_result);
}
return result;
}
static CURLcode parse_connect_to_slist(struct Curl_easy *data,
struct connectdata *conn,
struct curl_slist *conn_to_host)
{
CURLcode result = CURLE_OK;
char *host = NULL;
int port = -1;
while(conn_to_host && !host && port == -1) {
result = parse_connect_to_string(data, conn, conn_to_host->data,
&host, &port);
if(result)
return result;
if(host && *host) {
conn->conn_to_host.rawalloc = host;
conn->conn_to_host.name = host;
conn->bits.conn_to_host = TRUE;
infof(data, "Connecting to hostname: %s", host);
}
else {
conn->bits.conn_to_host = FALSE;
Curl_safefree(host);
}
if(port >= 0) {
conn->conn_to_port = port;
conn->bits.conn_to_port = TRUE;
infof(data, "Connecting to port: %d", port);
}
else {
conn->bits.conn_to_port = FALSE;
port = -1;
}
conn_to_host = conn_to_host->next;
}
#ifndef CURL_DISABLE_ALTSVC
if(data->asi && !host && (port == -1) &&
((conn->handler->protocol == CURLPROTO_HTTPS) ||
#ifdef DEBUGBUILD
getenv("CURL_ALTSVC_HTTP")
#else
0
#endif
)) {
enum alpnid srcalpnid = ALPN_none;
bool hit = FALSE;
struct altsvc *as = NULL;
int allowed_alpns = ALPN_none;
struct http_negotiation *neg = &data->state.http_neg;
DEBUGF(infof(data, "Alt-svc check wanted=%x, allowed=%x",
neg->wanted, neg->allowed));
#ifdef USE_HTTP3
if(neg->allowed & CURL_HTTP_V3x)
allowed_alpns |= ALPN_h3;
#endif
#ifdef USE_HTTP2
if(neg->allowed & CURL_HTTP_V2x)
allowed_alpns |= ALPN_h2;
#endif
if(neg->allowed & CURL_HTTP_V1x)
allowed_alpns |= ALPN_h1;
allowed_alpns &= (int)data->asi->flags;
host = conn->host.rawalloc;
DEBUGF(infof(data, "check Alt-Svc for host %s", host));
#ifdef USE_HTTP3
if(!hit && (neg->wanted & CURL_HTTP_V3x)) {
srcalpnid = ALPN_h3;
hit = Curl_altsvc_lookup(data->asi,
ALPN_h3, host, conn->remote_port,
&as ,
allowed_alpns);
}
#endif
#ifdef USE_HTTP2
if(!hit && (neg->wanted & CURL_HTTP_V2x) &&
!neg->h2_prior_knowledge) {
srcalpnid = ALPN_h2;
hit = Curl_altsvc_lookup(data->asi,
ALPN_h2, host, conn->remote_port,
&as ,
allowed_alpns);
}
#endif
if(!hit && (neg->wanted & CURL_HTTP_V1x) &&
!neg->only_10) {
srcalpnid = ALPN_h1;
hit = Curl_altsvc_lookup(data->asi,
ALPN_h1, host, conn->remote_port,
&as ,
allowed_alpns);
}
if(hit) {
char *hostd = strdup((char *)as->dst.host);
if(!hostd)
return CURLE_OUT_OF_MEMORY;
conn->conn_to_host.rawalloc = hostd;
conn->conn_to_host.name = hostd;
conn->bits.conn_to_host = TRUE;
conn->conn_to_port = as->dst.port;
conn->bits.conn_to_port = TRUE;
conn->bits.altused = TRUE;
infof(data, "Alt-svc connecting from [%s]%s:%d to [%s]%s:%d",
Curl_alpnid2str(srcalpnid), host, conn->remote_port,
Curl_alpnid2str(as->dst.alpnid), hostd, as->dst.port);
if(srcalpnid != as->dst.alpnid) {
switch(as->dst.alpnid) {
case ALPN_h1:
neg->wanted = neg->allowed = CURL_HTTP_V1x;
neg->only_10 = FALSE;
break;
case ALPN_h2:
neg->wanted = neg->allowed = CURL_HTTP_V2x;
break;
case ALPN_h3:
conn->transport_wanted = TRNSPRT_QUIC;
neg->wanted = neg->allowed = CURL_HTTP_V3x;
break;
default:
break;
}
}
}
}
#endif
return result;
}
#ifdef USE_UNIX_SOCKETS
static CURLcode resolve_unix(struct Curl_easy *data,
struct connectdata *conn,
char *unix_path,
struct Curl_dns_entry **pdns)
{
struct Curl_dns_entry *hostaddr;
bool longpath = FALSE;
DEBUGASSERT(unix_path);
*pdns = NULL;
hostaddr = calloc(1, sizeof(struct Curl_dns_entry));
if(!hostaddr)
return CURLE_OUT_OF_MEMORY;
hostaddr->addr = Curl_unix2addr(unix_path, &longpath,
conn->bits.abstract_unix_socket);
if(!hostaddr->addr) {
if(longpath)
failf(data, "Unix socket path too long: '%s'", unix_path);
free(hostaddr);
return longpath ? CURLE_COULDNT_RESOLVE_HOST : CURLE_OUT_OF_MEMORY;
}
hostaddr->refcount = 1;
*pdns = hostaddr;
return CURLE_OK;
}
#endif
static CURLcode resolve_server(struct Curl_easy *data,
struct connectdata *conn,
bool *async,
struct Curl_dns_entry **pdns)
{
struct hostname *ehost;
int eport;
timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE);
const char *peertype = "host";
CURLcode result;
*pdns = NULL;
#ifdef USE_UNIX_SOCKETS
{
char *unix_path = conn->unix_domain_socket;
#ifndef CURL_DISABLE_PROXY
if(!unix_path && CONN_IS_PROXIED(conn) && conn->socks_proxy.host.name &&
!strncmp(UNIX_SOCKET_PREFIX"/",
conn->socks_proxy.host.name, sizeof(UNIX_SOCKET_PREFIX)))
unix_path = conn->socks_proxy.host.name + sizeof(UNIX_SOCKET_PREFIX) - 1;
#endif
if(unix_path) {
conn->transport_wanted = TRNSPRT_UNIX;
return resolve_unix(data, conn, unix_path, pdns);
}
}
#endif
#ifndef CURL_DISABLE_PROXY
if(CONN_IS_PROXIED(conn)) {
ehost = conn->bits.socksproxy ? &conn->socks_proxy.host :
&conn->http_proxy.host;
eport = conn->bits.socksproxy ? conn->socks_proxy.port :
conn->http_proxy.port;
peertype = "proxy";
}
else
#endif
{
ehost = conn->bits.conn_to_host ? &conn->conn_to_host : &conn->host;
eport = conn->bits.conn_to_port ? conn->conn_to_port : conn->remote_port;
}
conn->hostname_resolve = strdup(ehost->name);
if(!conn->hostname_resolve)
return CURLE_OUT_OF_MEMORY;
result = Curl_resolv_timeout(data, conn->hostname_resolve,
eport, conn->ip_version,
pdns, timeout_ms);
DEBUGASSERT(!result || !*pdns);
if(result == CURLE_AGAIN) {
*async = TRUE;
return CURLE_OK;
}
else if(result == CURLE_OPERATION_TIMEDOUT) {
failf(data, "Failed to resolve %s '%s' with timeout after %"
FMT_TIMEDIFF_T " ms", peertype, ehost->dispname,
curlx_timediff(curlx_now(), data->progress.t_startsingle));
return CURLE_OPERATION_TIMEDOUT;
}
else if(result) {
failf(data, "Could not resolve %s: %s", peertype, ehost->dispname);
return result;
}
DEBUGASSERT(*pdns);
return CURLE_OK;
}
static void url_move_hostname(struct hostname *dest, struct hostname *src)
{
Curl_safefree(dest->rawalloc);
Curl_free_idnconverted_hostname(dest);
*dest = *src;
memset(src, 0, sizeof(*src));
}
static void reuse_conn(struct Curl_easy *data,
struct connectdata *temp,
struct connectdata *existing)
{
if(temp->user) {
free(existing->user);
free(existing->passwd);
existing->user = temp->user;
existing->passwd = temp->passwd;
temp->user = NULL;
temp->passwd = NULL;
}
#ifndef CURL_DISABLE_PROXY
existing->bits.proxy_user_passwd = temp->bits.proxy_user_passwd;
if(existing->bits.proxy_user_passwd) {
free(existing->http_proxy.user);
free(existing->socks_proxy.user);
free(existing->http_proxy.passwd);
free(existing->socks_proxy.passwd);
existing->http_proxy.user = temp->http_proxy.user;
existing->socks_proxy.user = temp->socks_proxy.user;
existing->http_proxy.passwd = temp->http_proxy.passwd;
existing->socks_proxy.passwd = temp->socks_proxy.passwd;
temp->http_proxy.user = NULL;
temp->socks_proxy.user = NULL;
temp->http_proxy.passwd = NULL;
temp->socks_proxy.passwd = NULL;
}
#endif
url_move_hostname(&existing->host, &temp->host);
url_move_hostname(&existing->conn_to_host, &temp->conn_to_host);
existing->conn_to_port = temp->conn_to_port;
existing->remote_port = temp->remote_port;
free(existing->hostname_resolve);
existing->hostname_resolve = temp->hostname_resolve;
temp->hostname_resolve = NULL;
existing->bits.reuse = TRUE;
Curl_conn_free(data, temp);
}
static void conn_meta_freeentry(void *p)
{
(void)p;
DEBUGASSERT(p == NULL);
}
static CURLcode create_conn(struct Curl_easy *data,
struct connectdata **in_connect,
bool *reusedp)
{
CURLcode result = CURLE_OK;
struct connectdata *conn;
struct connectdata *existing = NULL;
bool reuse;
bool connections_available = TRUE;
bool force_reuse = FALSE;
bool waitpipe = FALSE;
*reusedp = FALSE;
*in_connect = NULL;
if(!data->state.url) {
result = CURLE_URL_MALFORMAT;
goto out;
}
conn = allocate_conn(data);
if(!conn) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
*in_connect = conn;
Curl_hash_init(&conn->meta_hash, 23,
Curl_hash_str, curlx_str_key_compare, conn_meta_freeentry);
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
#ifndef CURL_DISABLE_PROXY
result = create_conn_helper_init_proxy(data, conn);
if(result)
goto out;
if((conn->given->flags&PROTOPT_SSL) && conn->bits.httpproxy)
conn->bits.tunnel_proxy = TRUE;
#endif
result = parse_remote_port(data, conn);
if(result)
goto out;
result = override_login(data, conn);
if(result)
goto out;
result = set_login(data, conn);
if(result)
goto out;
result = parse_connect_to_slist(data, conn, data->set.connect_to);
if(result)
goto out;
#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;
}
if(conn->bits.conn_to_host &&
curl_strequal(conn->conn_to_host.name, conn->host.name)) {
conn->bits.conn_to_host = FALSE;
}
if(conn->bits.conn_to_port && conn->conn_to_port == conn->remote_port) {
conn->bits.conn_to_port = FALSE;
}
#ifndef CURL_DISABLE_PROXY
if((conn->bits.conn_to_host || conn->bits.conn_to_port) &&
conn->bits.httpproxy)
conn->bits.tunnel_proxy = TRUE;
#endif
result = setup_connection_internals(data, conn);
if(result)
goto out;
#ifndef CURL_DISABLE_FILE
if(conn->handler->flags & PROTOPT_NONETWORK) {
bool done;
DEBUGASSERT(conn->handler->connect_it);
data->info.conn_scheme = conn->handler->scheme;
data->info.conn_protocol = (conn->handler->protocol) & CURLPROTO_MASK;
result = conn->handler->connect_it(data, &done);
if(result)
goto out;
Curl_attach_connection(data, conn);
result = Curl_cpool_add(data, conn);
if(!result) {
result = setup_range(data);
if(!result) {
Curl_xfer_setup_nop(data);
result = Curl_init_do(data, conn);
}
}
if(result) {
DEBUGASSERT(conn->handler->done);
(void)conn->handler->done(data, result, FALSE);
}
goto out;
}
#endif
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;
result = Curl_ssl_easy_config_complete(data);
if(result)
goto out;
Curl_cpool_prune_dead(data);
DEBUGASSERT(conn->user);
DEBUGASSERT(conn->passwd);
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) {
bool tls_upgraded = (!(conn->given->flags & PROTOPT_SSL) &&
Curl_conn_is_ssl(conn, FIRSTSOCKET));
reuse_conn(data, conn, existing);
conn = existing;
*in_connect = conn;
#ifndef CURL_DISABLE_PROXY
infof(data, "Reusing existing %s: connection%s with %s %s",
conn->given->scheme,
tls_upgraded ? " (upgraded to SSL)" : "",
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, "Reusing existing %s: connection%s with host %s",
conn->given->scheme,
tls_upgraded ? " (upgraded to SSL)" : "",
conn->host.dispname);
#endif
}
else {
if(conn->handler->flags & PROTOPT_ALPN) {
if(data->set.ssl_enable_alpn)
conn->bits.tls_enable_alpn = TRUE;
}
if(waitpipe) {
infof(data, "Waiting on connection to negotiate possible multiplexing.");
connections_available = FALSE;
}
else {
switch(Curl_cpool_check_limits(data, conn)) {
case CPOOL_LIMIT_DEST:
infof(data, "No more connections allowed to host");
connections_available = FALSE;
break;
case CPOOL_LIMIT_TOTAL:
if(data->master_mid != UINT_MAX)
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;
}
break;
default:
break;
}
}
if(!connections_available) {
Curl_conn_free(data, conn);
*in_connect = NULL;
result = CURLE_NO_CONNECTION_AVAILABLE;
goto out;
}
else {
result = Curl_ssl_conn_config_init(data, conn);
if(result) {
DEBUGF(curl_mfprintf(stderr, "Error: init connection ssl config\n"));
goto out;
}
Curl_attach_connection(data, conn);
result = Curl_cpool_add(data, conn);
if(result)
goto out;
}
#ifdef USE_NTLM
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
}
result = Curl_init_do(data, conn);
if(result)
goto out;
result = setup_range(data);
if(result)
goto out;
if(conn->bits.reuse) {
*reusedp = TRUE;
}
data->info.conn_scheme = conn->handler->scheme;
data->info.conn_protocol = (conn->handler->protocol) & CURLPROTO_MASK;
data->info.used_proxy =
#ifdef CURL_DISABLE_PROXY
0
#else
conn->bits.proxy
#endif
;
result = Curl_conn_ev_data_setup(data);
out:
return result;
}
CURLcode Curl_setup_conn(struct Curl_easy *data,
struct Curl_dns_entry *dns,
bool *protocol_done)
{
CURLcode result = CURLE_OK;
struct connectdata *conn = data->conn;
DEBUGASSERT(dns);
Curl_pgrsTime(data, TIMER_NAMELOOKUP);
if(!conn->bits.reuse)
result = Curl_conn_setup(data, conn, FIRSTSOCKET, dns,
CURL_CF_SSL_DEFAULT);
if(!result)
result = Curl_headers_init(data);
*protocol_done = FALSE;
return result;
}
CURLcode Curl_connect(struct Curl_easy *data,
bool *asyncp,
bool *protocol_done)
{
CURLcode result;
struct connectdata *conn;
bool reused = FALSE;
*asyncp = FALSE;
*protocol_done = FALSE;
Curl_req_hard_reset(&data->req, data);
result = create_conn(data, &conn, &reused);
if(result == CURLE_NO_CONNECTION_AVAILABLE) {
DEBUGASSERT(!conn);
return result;
}
if(!result) {
DEBUGASSERT(conn);
if(reused) {
if(CONN_ATTACHED(conn) > 1)
*protocol_done = TRUE;
}
else if(conn->handler->flags & PROTOPT_NONETWORK) {
*asyncp = FALSE;
Curl_pgrsTime(data, TIMER_NAMELOOKUP);
*protocol_done = TRUE;
}
else {
struct Curl_dns_entry *dns;
result = resolve_server(data, conn, asyncp, &dns);
if(!result) {
*asyncp = !dns;
if(dns)
result = Curl_setup_conn(data, dns, protocol_done);
}
}
}
if(result && conn) {
Curl_detach_connection(data);
Curl_conn_terminate(data, conn, TRUE);
}
return result;
}
CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn)
{
CURLcode result;
if(conn) {
conn->bits.do_more = FALSE;
if(data->state.wildcardmatch &&
!(conn->handler->flags & PROTOPT_WILDCARD))
data->state.wildcardmatch = FALSE;
}
data->state.done = FALSE;
if(data->req.no_body)
data->state.httpreq = HTTPREQ_HEAD;
result = Curl_req_start(&data->req, data);
if(!result) {
Curl_speedinit(data);
Curl_pgrsSetUploadCounter(data, 0);
Curl_pgrsSetDownloadCounter(data, 0);
}
return result;
}
#if defined(USE_HTTP2) || defined(USE_HTTP3)
#ifdef USE_NGHTTP2
static void priority_remove_child(struct Curl_easy *parent,
struct Curl_easy *child)
{
struct Curl_data_prio_node **pnext = &parent->set.priority.children;
struct Curl_data_prio_node *pnode = parent->set.priority.children;
DEBUGASSERT(child->set.priority.parent == parent);
while(pnode && pnode->data != child) {
pnext = &pnode->next;
pnode = pnode->next;
}
DEBUGASSERT(pnode);
if(pnode) {
*pnext = pnode->next;
free(pnode);
}
child->set.priority.parent = 0;
child->set.priority.exclusive = FALSE;
}
CURLcode Curl_data_priority_add_child(struct Curl_easy *parent,
struct Curl_easy *child,
bool exclusive)
{
if(child->set.priority.parent) {
priority_remove_child(child->set.priority.parent, child);
}
if(parent) {
struct Curl_data_prio_node **tail;
struct Curl_data_prio_node *pnode;
pnode = calloc(1, sizeof(*pnode));
if(!pnode)
return CURLE_OUT_OF_MEMORY;
pnode->data = child;
if(parent->set.priority.children && exclusive) {
struct Curl_data_prio_node *node = parent->set.priority.children;
while(node) {
node->data->set.priority.parent = child;
node = node->next;
}
tail = &child->set.priority.children;
while(*tail)
tail = &(*tail)->next;
DEBUGASSERT(!*tail);
*tail = parent->set.priority.children;
parent->set.priority.children = 0;
}
tail = &parent->set.priority.children;
while(*tail) {
(*tail)->data->set.priority.exclusive = FALSE;
tail = &(*tail)->next;
}
DEBUGASSERT(!*tail);
*tail = pnode;
}
child->set.priority.parent = parent;
child->set.priority.exclusive = exclusive;
return CURLE_OK;
}
#endif
#ifdef USE_NGHTTP2
static void data_priority_cleanup(struct Curl_easy *data)
{
while(data->set.priority.children) {
struct Curl_easy *tmp = data->set.priority.children->data;
priority_remove_child(data, tmp);
if(data->set.priority.parent)
Curl_data_priority_add_child(data->set.priority.parent, tmp, FALSE);
}
if(data->set.priority.parent)
priority_remove_child(data->set.priority.parent, data);
}
#endif
void Curl_data_priority_clear_state(struct Curl_easy *data)
{
memset(&data->state.priority, 0, sizeof(data->state.priority));
}
#endif
CURLcode Curl_conn_meta_set(struct connectdata *conn, const char *key,
void *meta_data, Curl_meta_dtor *meta_dtor)
{
if(!Curl_hash_add2(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1,
meta_data, meta_dtor)) {
meta_dtor(CURL_UNCONST(key), strlen(key) + 1, meta_data);
return CURLE_OUT_OF_MEMORY;
}
return CURLE_OK;
}
void Curl_conn_meta_remove(struct connectdata *conn, const char *key)
{
Curl_hash_delete(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1);
}
void *Curl_conn_meta_get(struct connectdata *conn, const char *key)
{
return Curl_hash_pick(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1);
}
CURLcode Curl_1st_err(CURLcode r1, CURLcode r2)
{
return r1 ? r1 : r2;
}
CURLcode Curl_1st_fatal(CURLcode r1, CURLcode r2)
{
if(r1 && (r1 != CURLE_AGAIN))
return r1;
if(r2 && (r2 != CURLE_AGAIN))
return r2;
return r1;
}