diff --git a/docs/internals/CREDENTIALS.md b/docs/internals/CREDENTIALS.md index 95c12cb47c..e5e409ed7b 100644 --- a/docs/internals/CREDENTIALS.md +++ b/docs/internals/CREDENTIALS.md @@ -38,9 +38,10 @@ suitable connection. For an `easy_perform()` this may happen several times if, for example, http redirects are followed. When an `easy_perform()` starts, the transfer's `data->state.initial_origin` -peer is cleared. When creating the connection, `conn->origin` is calculated -(e.g. who the request talks to). If `data->state.initial_origin` is not -set, the first `conn->origin` is linked there. Now `libcurl` knows where +peer is cleared. When creating the connection, `data->state.origin` is +calculated (e.g. who the request talks to). If `data->state.initial_origin` +is not set, the first `data->state.origin` is linked there. +Now `libcurl` knows where the transfer initially talked to on all possible subsequent requests. Credential information from `CURLOPT_*` settings is only applicable for the diff --git a/docs/internals/PEERS.md b/docs/internals/PEERS.md index 7eb2bc000c..28d002a926 100644 --- a/docs/internals/PEERS.md +++ b/docs/internals/PEERS.md @@ -20,13 +20,25 @@ A `peer` in curl internals is represented by a `struct Curl_peer`. It has the fo A peer, in short, is a communication endpoint. +## peers and transfers + +The peer a transfer, e.g. easy handle, works against is determined at the +start of each request. It is kept in `data->state.origin`. For the first +request done in a `curl_easy_perform()` or equivalent, this origin is +linked to `data->state.initial_origin`. This allows checks if properties +of `data->set.*` should apply to a request or not. + +`data->state.origin` is relevant for cookie processing, signing requests +and other request/response based processing. + ## peers and connections A network connection always goes *somewhere*. That *somewhere* is called the `origin` of the connection (e.g. the source of responses/downloads). It is kept in `conn->origin` and is always present in a connection. -The `origin` is *logical* endpoint a connection talks to. +The `origin` is *logical* endpoint a connection talks to. In most +configurations it is the same as `data->state.origin` (see proxies below). For most connections, the `origin` is connected to *directly*. It can be directed to another peer, however. @@ -56,6 +68,19 @@ might connect as: 5. curl --> socks_proxy.peer --> http_proxy.peer --> conn->via_peer/origin ``` +A `conn->(socks|http)_proxy.peer` is only ever present when the proxy +is in use and `NULL` otherwise. + +SOCKS proxies are always used for tunneling, either to the origin or +the HTTP proxy. They operate in a connection filter. + +HTTP proxies can operate in two modes: tunneling or forwarding. When tunneling, +they also operate in a connection filter. In forwarding mode however, they +become the `origin` the connection talks to. + +Therefore, connections that talk to a forwarding HTTP proxy have `conn->origin` +set to `conn->http_proxy.peer` and `conn->bits.origin_is_proxy` is set. + The connection filter `SETUP`, that assembles the filters for a connection, figures out which peer to pass to which filter in order to make it all work. The individual filters get passed a specific peer and do not need be concerned diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 1699fc5653..90e33e044b 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -241,7 +241,6 @@ LIB_CFILES = \ multi_ev.c \ multi_ntfy.c \ netrc.c \ - noproxy.c \ openldap.c \ parsedate.c \ peer.c \ @@ -249,6 +248,7 @@ LIB_CFILES = \ pop3.c \ progress.c \ protocol.c \ + proxy.c \ psl.c \ rand.c \ ratelimit.c \ @@ -375,13 +375,13 @@ LIB_HFILES = \ multi_ntfy.h \ multiif.h \ netrc.h \ - noproxy.h \ parsedate.h \ peer.h \ pingpong.h \ pop3.h \ progress.h \ protocol.h \ + proxy.h \ psl.h \ rand.h \ ratelimit.h \ diff --git a/lib/cf-ip-happy.c b/lib/cf-ip-happy.c index 53b79e2fd4..68feca3062 100644 --- a/lib/cf-ip-happy.c +++ b/lib/cf-ip-happy.c @@ -713,14 +713,15 @@ static CURLcode is_connected(struct Curl_cfilter *cf, return CURLE_FAILED_INIT; #ifndef CURL_DISABLE_PROXY - if(conn->bits.socksproxy) + if(conn->socks_proxy.peer) proxy_peer = conn->socks_proxy.peer; - else if(conn->bits.httpproxy) + else if(conn->http_proxy.peer) proxy_peer = conn->http_proxy.peer; #endif viamsg[0] = 0; - if((peer != conn->origin) && (peer != proxy_peer)) { + if(!Curl_peer_equal(peer, conn->origin) && + !Curl_peer_equal(peer, proxy_peer)) { #ifdef USE_UNIX_SOCKETS if(peer->unix_socket) curl_msnprintf(viamsg, sizeof(viamsg), " over unix://%s", diff --git a/lib/cf-socket.c b/lib/cf-socket.c index fb9160a391..b1447c20f0 100644 --- a/lib/cf-socket.c +++ b/lib/cf-socket.c @@ -1634,8 +1634,6 @@ static void cf_socket_update_data(struct Curl_cfilter *cf, if(cf->connected && (cf->sockindex == FIRSTSOCKET)) { struct cf_socket_ctx *ctx = cf->ctx; data->info.primary = ctx->ip; - /* not sure if this is redundant... */ - data->info.conn_remote_port = cf->conn->origin->port; } } diff --git a/lib/cfilters.c b/lib/cfilters.c index 70996fceac..5ed22213c9 100644 --- a/lib/cfilters.c +++ b/lib/cfilters.c @@ -660,6 +660,31 @@ bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex) return FALSE; } +#ifndef CURL_DISABLE_PROXY +static bool cf_is_tunneling(struct Curl_cfilter *cf) +{ + for(; cf; cf = cf->next) { + if((cf->cft->flags & CF_TYPE_PROXY)) + return TRUE; + } + return FALSE; +} + +bool Curl_conn_is_tunneling(struct connectdata *conn, int sockindex) +{ + if(!CONN_SOCK_IDX_VALID(sockindex)) + return FALSE; + return conn ? cf_is_tunneling(conn->cfilter[sockindex]) : FALSE; +} +#else +bool Curl_conn_is_tunneling(struct connectdata *conn, int sockindex) +{ + (void)conn; + (void)sockindex; + return FALSE; +} +#endif /* CURL_DISABLE_PROXY */ + static bool cf_is_ssl(struct Curl_cfilter *cf) { for(; cf; cf = cf->next) { diff --git a/lib/cfilters.h b/lib/cfilters.h index 13bb428b55..f3e19087b5 100644 --- a/lib/cfilters.h +++ b/lib/cfilters.h @@ -393,6 +393,10 @@ bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex); */ bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex); +/* Determine if the connection has one or more proxy filters. + * e.g. is tunneling. */ +bool Curl_conn_is_tunneling(struct connectdata *conn, int sockindex); + /* * Fill `info` with information about the TLS instance securing the connection * when available, otherwise e.g. when Curl_conn_is_ssl() is FALSE, return diff --git a/lib/connect.c b/lib/connect.c index d3533d4767..0e864eca1e 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -319,11 +319,11 @@ static CURLcode cf_setup_add_socks(struct Curl_cfilter *cf, { struct cf_setup_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; - if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->bits.socksproxy) { + if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->socks_proxy.peer) { /* Add a SOCKS proxy to go through `first_peer` to `second_peer`*/ struct Curl_peer *second_peer; - if(cf->conn->bits.httpproxy) + if(cf->conn->http_proxy.peer) second_peer = cf->conn->http_proxy.peer; else second_peer = Curl_conn_get_destination(cf->conn, cf->sockindex); @@ -353,9 +353,14 @@ static CURLcode cf_setup_add_http_proxy(struct Curl_cfilter *cf, struct cf_setup_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; - if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && cf->conn->bits.httpproxy) { + if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && + cf->conn->http_proxy.peer && !cf->conn->bits.origin_is_proxy) { + struct Curl_peer *peer = cf->conn->http_proxy.peer; + struct Curl_peer *tunnel_peer = + Curl_conn_get_destination(cf->conn, cf->sockindex); + #ifdef USE_SSL - if(IS_HTTPS_PROXY(cf->conn->http_proxy.proxytype) && + if(CURL_PROXY_IS_HTTPS(cf->conn->http_proxy.proxytype) && !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { result = Curl_cf_ssl_proxy_insert_after( cf, data, cf->conn->http_proxy.peer); @@ -368,20 +373,15 @@ static CURLcode cf_setup_add_http_proxy(struct Curl_cfilter *cf, } #endif /* USE_SSL */ - if(cf->conn->bits.tunnel_proxy) { - struct Curl_peer *peer = cf->conn->http_proxy.peer; - struct Curl_peer *tunnel_peer; /* where HTTP should tunnel to */ - tunnel_peer = Curl_conn_get_destination(cf->conn, cf->sockindex); - result = Curl_cf_http_proxy_insert_after( - cf, data, peer, tunnel_peer, - ctx->transport, cf->conn->http_proxy.proxytype); - if(result) { - CURL_TRC_CF(data, cf, "adding HTTP proxy tunnel filter failed -> %d", - (int)result); - return result; - } - CURL_TRC_CF(data, cf, "added HTTP proxy tunnel filter"); + result = Curl_cf_http_proxy_insert_after( + cf, data, peer, tunnel_peer, + ctx->transport, cf->conn->http_proxy.proxytype); + if(result) { + CURL_TRC_CF(data, cf, "adding HTTP proxy tunnel filter failed -> %d", + (int)result); + return result; } + CURL_TRC_CF(data, cf, "added HTTP proxy tunnel filter"); ctx->state = CF_SETUP_CNNCT_HTTP_PROXY; } return result; @@ -424,11 +424,11 @@ static CURLcode cf_setup_add_ip_happy(struct Curl_cfilter *cf, return CURLE_FAILED_INIT; #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) - if(cf->conn->bits.httpproxy && cf->conn->bits.tunnel_proxy) { + if(cf->conn->http_proxy.peer && !cf->conn->bits.origin_is_proxy) { first_transport = Curl_http_proxy_transport(cf->conn->http_proxy.proxytype); tunnel_peer = Curl_conn_get_destination(cf->conn, cf->sockindex); - if((first_transport == TRNSPRT_QUIC) && (cf->conn->bits.socksproxy)) { + if((first_transport == TRNSPRT_QUIC) && cf->conn->socks_proxy.peer) { failf(data, "HTTP/3 proxy not possible via SOCKS"); return CURLE_UNSUPPORTED_PROTOCOL; } @@ -472,8 +472,8 @@ static CURLcode cf_setup_add_origin_filters(struct Curl_cfilter *cf, /* Wanting QUIC with a HTTP tunneling filter, we now need to add * the QUIC filter on top. Without tunneling, this has already * happened in the Happy Eyeball filter. */ - if(ctx->transport == TRNSPRT_QUIC && cf->conn->bits.httpproxy && - cf->conn->bits.tunnel_proxy) { + if(ctx->transport == TRNSPRT_QUIC && + cf->conn->http_proxy.peer && !cf->conn->bits.origin_is_proxy) { struct Curl_peer *origin = Curl_conn_get_origin(cf->conn, cf->sockindex); struct Curl_peer *peer = Curl_conn_get_destination(cf->conn, cf->sockindex); @@ -498,13 +498,21 @@ static CURLcode cf_setup_add_origin_filters(struct Curl_cfilter *cf, (ctx->ssl_mode != CURL_CF_SSL_DISABLE && cf->conn->scheme->flags & PROTOPT_SSL)) && /* we want SSL */ !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { /* it is missing */ - /* Another FTP quirk: when adding SSL verification, to a DATA - * connection, always verify against the control's origin */ - struct Curl_peer *origin = Curl_conn_get_origin(cf->conn, FIRSTSOCKET); - struct Curl_peer *peer = - Curl_conn_get_destination(cf->conn, cf->sockindex); - result = Curl_cf_ssl_insert_after(cf, data, origin, peer); +#ifndef CURL_DISABLE_PROXY + if(cf->conn->bits.origin_is_proxy) { + result = Curl_cf_ssl_proxy_insert_after(cf, data, cf->conn->origin); + } + else +#endif + { + /* Another FTP quirk: when adding SSL verification, to a DATA + * connection, always verify against the control's origin */ + struct Curl_peer *origin = Curl_conn_get_origin(cf->conn, FIRSTSOCKET); + struct Curl_peer *peer = + Curl_conn_get_destination(cf->conn, cf->sockindex); + result = Curl_cf_ssl_insert_after(cf, data, origin, peer); + } if(result) { CURL_TRC_CF(data, cf, "adding SSL filter for origin failed -> %d", (int)result); @@ -766,10 +774,6 @@ struct Curl_peer *Curl_conn_get_origin(struct connectdata *conn, struct Curl_peer *Curl_conn_get_destination(struct connectdata *conn, int sockindex) { -#ifndef CURL_DISABLE_PROXY - if(conn->http_proxy.peer && !conn->bits.tunnel_proxy) - return conn->http_proxy.peer; -#endif return (sockindex == SECONDARYSOCKET) ? (conn->via_peer2 ? conn->via_peer2 : conn->origin2) : (conn->via_peer ? conn->via_peer : conn->origin); diff --git a/lib/ftp.c b/lib/ftp.c index ef154f99f7..3200e20674 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -1983,11 +1983,7 @@ static CURLcode ftp_epsv_disable(struct Curl_easy *data, { CURLcode result = CURLE_OK; - if(conn->bits.ipv6 -#ifndef CURL_DISABLE_PROXY - && !(conn->bits.tunnel_proxy || conn->bits.socksproxy) -#endif - ) { + if(conn->bits.ipv6 && !Curl_conn_is_tunneling(conn, FIRSTSOCKET)) { /* We cannot disable EPSV when doing IPv6, so this is instead a fail */ failf(data, "Failed EPSV attempt, exiting"); return CURLE_WEIRD_SERVER_REPLY; @@ -2019,7 +2015,7 @@ static CURLcode ftp_control_addr_dup(struct Curl_easy *data, char **newhostp) the effective control connection address is the proxy address, not the ftp host. */ #ifndef CURL_DISABLE_PROXY - if(conn->bits.tunnel_proxy || conn->bits.socksproxy) + if(Curl_conn_is_tunneling(conn, FIRSTSOCKET)) *newhostp = curlx_strdup(conn->origin->hostname); else #endif diff --git a/lib/http.c b/lib/http.c index cf9177922d..2e94b32640 100644 --- a/lib/http.c +++ b/lib/http.c @@ -154,7 +154,7 @@ char *Curl_checkProxyheaders(struct Curl_easy *data, { struct curl_slist *head; - for(head = (conn->bits.proxy && data->set.sep_headers) ? + for(head = (conn->http_proxy.peer && data->set.sep_headers) ? data->set.proxyheaders : data->set.headers; head; head = head->next) { if(curl_strnequal(head->data, thisheader, thislen) && @@ -783,7 +783,7 @@ CURLcode Curl_http_output_auth(struct Curl_easy *data, if( #ifndef CURL_DISABLE_PROXY - (!conn->bits.httpproxy || !conn->http_proxy.creds) && + (!conn->http_proxy.peer || !conn->http_proxy.creds) && #endif #ifdef USE_SPNEGO !(authhost->want & CURLAUTH_NEGOTIATE) && @@ -820,7 +820,7 @@ CURLcode Curl_http_output_auth(struct Curl_easy *data, #ifndef CURL_DISABLE_PROXY /* Send proxy authentication header if needed */ - if(conn->bits.httpproxy && (!conn->bits.tunnel_proxy || is_connect)) { + if(conn->bits.origin_is_proxy || is_connect) { result = output_auth_headers(data, conn, authproxy, request, path_and_query, TRUE); if(result) @@ -1736,8 +1736,7 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, if(is_connect) proxy = HEADER_CONNECT; else - proxy = data->conn->bits.httpproxy && !data->conn->bits.tunnel_proxy ? - HEADER_PROXY : HEADER_SERVER; + proxy = data->conn->bits.origin_is_proxy ? HEADER_PROXY : HEADER_SERVER; switch(proxy) { case HEADER_SERVER: @@ -1998,8 +1997,9 @@ static CURLcode http_set_aptr_host(struct Curl_easy *data) #endif ptr = Curl_checkheaders(data, STRCONST("Host")); - if(ptr && (!data->state.this_is_a_follow || - Curl_peer_equal(data->state.initial_origin, conn->origin))) { + if(ptr && + (!data->state.this_is_a_follow || + Curl_peer_equal(data->state.initial_origin, data->state.origin))) { #ifndef CURL_DISABLE_COOKIES /* If we have a given custom Host: header, we extract the hostname in order to possibly use it for cookie reasons later on. We only allow the @@ -2043,18 +2043,19 @@ static CURLcode http_set_aptr_host(struct Curl_easy *data) } else { /* Use the hostname as present in the URL if it was IPv6. */ - char *host = (conn->origin->user_hostname[0] == '[') ? - conn->origin->user_hostname : conn->origin->hostname; + char *host = (data->state.origin->user_hostname[0] == '[') ? + data->state.origin->user_hostname : data->state.origin->hostname; if(((conn->given->protocol & (CURLPROTO_HTTPS | CURLPROTO_WSS)) && - (conn->origin->port == PORT_HTTPS)) || + (data->state.origin->port == PORT_HTTPS)) || ((conn->given->protocol & (CURLPROTO_HTTP | CURLPROTO_WS)) && - (conn->origin->port == PORT_HTTP))) + (data->state.origin->port == PORT_HTTP))) /* if(HTTPS on port 443) OR (HTTP on port 80) then do not include the port number in the host string */ aptr->host = curl_maprintf("Host: %s\r\n", host); else - aptr->host = curl_maprintf("Host: %s:%d\r\n", host, conn->origin->port); + aptr->host = curl_maprintf("Host: %s:%d\r\n", + host, data->state.origin->port); if(!aptr->host) /* without Host: we cannot make a nice request */ @@ -2082,7 +2083,7 @@ static CURLcode http_target(struct Curl_easy *data, } #ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { + if(conn->bits.origin_is_proxy) { /* Using a proxy but does not tunnel through it */ /* The path sent to the proxy is in fact the entire URL, but if the remote @@ -2096,8 +2097,8 @@ static CURLcode http_target(struct Curl_easy *data, if(!h) return CURLE_OUT_OF_MEMORY; - if(conn->origin->user_hostname != conn->origin->hostname) { - uc = curl_url_set(h, CURLUPART_HOST, conn->origin->hostname, 0); + if(data->state.origin->user_hostname != data->state.origin->hostname) { + uc = curl_url_set(h, CURLUPART_HOST, data->state.origin->hostname, 0); if(uc) { curl_url_cleanup(h); return CURLE_OUT_OF_MEMORY; @@ -2541,7 +2542,7 @@ static CURLcode http_cookies(struct Curl_easy *data, if(data->cookies && data->state.cookie_engine) { bool okay; const char *host = data->req.cookiehost ? - data->req.cookiehost : data->conn->origin->hostname; + data->req.cookiehost : data->state.origin->hostname; Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); result = Curl_cookie_getlist(data, data->conn, &okay, host, &list); if(!result && okay) { @@ -2728,8 +2729,7 @@ static CURLcode http_check_new_conn(struct Curl_easy *data) alpn = Curl_conn_get_alpn_negotiated(data, conn); if(alpn && !strcmp("h3", alpn)) { #ifndef CURL_DISABLE_PROXY - if((Curl_conn_http_version(data, conn) == 30) || !conn->bits.proxy || - conn->bits.tunnel_proxy) + if(!conn->bits.origin_is_proxy) #endif DEBUGASSERT(Curl_conn_http_version(data, conn) == 30); info_version = "HTTP/3"; @@ -2737,7 +2737,7 @@ static CURLcode http_check_new_conn(struct Curl_easy *data) else if(alpn && !strcmp("h2", alpn)) { #ifndef CURL_DISABLE_PROXY if((Curl_conn_http_version(data, conn) != 20) && - conn->bits.proxy && !conn->bits.tunnel_proxy) { + conn->bits.origin_is_proxy) { result = Curl_http2_switch(data); if(result) return result; @@ -2946,8 +2946,7 @@ static CURLcode http_add_hd(struct Curl_easy *data, #ifndef CURL_DISABLE_PROXY case H1_HD_PROXY_CONNECTION: - if(conn->bits.httpproxy && - !conn->bits.tunnel_proxy && + if(conn->bits.origin_is_proxy && !Curl_checkheaders(data, STRCONST("Proxy-Connection")) && !Curl_checkProxyheaders(data, data->conn, STRCONST("Proxy-Connection"))) result = curlx_dyn_add(req, "Proxy-Connection: Keep-Alive\r\n"); @@ -3190,7 +3189,6 @@ static CURLcode http_header_a(struct Curl_easy *data, { #ifndef CURL_DISABLE_ALTSVC const char *v; - struct connectdata *conn = data->conn; v = (data->asi && (Curl_xfer_is_secure(data) || #ifdef DEBUGBUILD @@ -3205,8 +3203,9 @@ static CURLcode http_header_a(struct Curl_easy *data, struct SingleRequest *k = &data->req; enum alpnid id = (k->httpversion == 30) ? ALPN_h3 : (k->httpversion == 20) ? ALPN_h2 : ALPN_h1; - return Curl_altsvc_parse(data, data->asi, v, id, conn->origin->hostname, - curlx_uitous((unsigned int)conn->origin->port)); + return Curl_altsvc_parse( + data, data->asi, v, id, data->state.origin->hostname, + curlx_uitous((unsigned int)data->state.origin->port)); } #else (void)data; @@ -3424,7 +3423,7 @@ static CURLcode http_header_p(struct Curl_easy *data, const char *v = HD_VAL(hd, hdlen, "Proxy-Connection:"); if(v) { struct connectdata *conn = data->conn; - if((k->httpversion == 10) && conn->bits.httpproxy && + if((k->httpversion == 10) && conn->http_proxy.peer && HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "keep-alive")) { /* * When an HTTP/1.0 reply comes when using a proxy, the @@ -3435,7 +3434,7 @@ static CURLcode http_header_p(struct Curl_easy *data, connkeep(conn, "Proxy-Connection keep-alive"); /* do not close */ infof(data, "HTTP/1.0 proxy connection set to keep alive"); } - else if((k->httpversion == 11) && conn->bits.httpproxy && + else if((k->httpversion == 11) && conn->http_proxy.peer && HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "close")) { /* * We get an HTTP/1.1 response from a proxy and it says it will @@ -3533,7 +3532,7 @@ static CURLcode http_header_s(struct Curl_easy *data, /* If there is a custom-set Host: name, use it here, or else use * real peer hostname. */ const char *host = data->req.cookiehost ? - data->req.cookiehost : conn->origin->hostname; + data->req.cookiehost : data->state.origin->hostname; const bool secure_context = Curl_secure_context(conn, host); CURLcode result; Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); @@ -3556,8 +3555,8 @@ static CURLcode http_header_s(struct Curl_easy *data, ) ) ? HD_VAL(hd, hdlen, "Strict-Transport-Security:") : NULL; if(v) { - CURLcode result = - Curl_hsts_parse(data->hsts, conn->origin->hostname, v); + CURLcode result = Curl_hsts_parse( + data->hsts, data->state.origin->hostname, v); if(result) { if(result == CURLE_OUT_OF_MEMORY) return result; diff --git a/lib/http2.c b/lib/http2.c index ec72671244..2133c5a6e0 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -1403,7 +1403,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, struct Curl_cfilter *cf = userp; struct cf_h2_ctx *ctx = cf->ctx; struct h2_stream_ctx *stream; - struct Curl_easy *data_s; + struct Curl_easy *data; int32_t stream_id = frame->hd.stream_id; CURLcode result; (void)flags; @@ -1411,15 +1411,15 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ /* get the stream from the hash based on Stream ID */ - data_s = nghttp2_session_get_stream_user_data(session, stream_id); - if(!GOOD_EASY_HANDLE(data_s)) + data = nghttp2_session_get_stream_user_data(session, stream_id); + if(!GOOD_EASY_HANDLE(data)) /* Receiving a Stream ID not in the hash should not happen, this is an internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; - stream = H2_STREAM_CTX(ctx, data_s); + stream = H2_STREAM_CTX(ctx, data); if(!stream) { - failf(data_s, "Internal NULL stream"); + failf(data, "Internal NULL stream"); return NGHTTP2_ERR_CALLBACK_FAILURE; } @@ -1432,14 +1432,14 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, !strncmp(HTTP_PSEUDO_AUTHORITY, (const char *)name, namelen)) { /* pseudo headers are lower case */ int rc = 0; - char *check = curl_maprintf("%s:%d", cf->conn->origin->hostname, - cf->conn->origin->port); + char *check = curl_maprintf("%s:%d", data->state.origin->hostname, + data->state.origin->port); if(!check) /* no memory */ return NGHTTP2_ERR_CALLBACK_FAILURE; if(!curl_strequal(check, (const char *)value) && - ((cf->conn->origin->port != cf->conn->given->defport) || - !curl_strequal(cf->conn->origin->hostname, (const char *)value))) { + ((data->state.origin->port != cf->conn->given->defport) || + !curl_strequal(data->state.origin->hostname, (const char *)value))) { /* This is push is not for the same authority that was asked for in * the URL. RFC 7540 section 8.2 says: "A client MUST treat a * PUSH_PROMISE for which the server is not authoritative as a stream @@ -1467,7 +1467,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, char **headp; if(stream->push_headers_alloc > 1000) { /* this is beyond crazy many headers, bail out */ - failf(data_s, "Too many PUSH_PROMISE headers"); + failf(data, "Too many PUSH_PROMISE headers"); free_push_headers(stream); return NGHTTP2_ERR_CALLBACK_FAILURE; } @@ -1491,13 +1491,13 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(stream->bodystarted) { /* This is a trailer */ - CURL_TRC_CF(data_s, cf, "[%d] trailer: %.*s: %.*s", + CURL_TRC_CF(data, cf, "[%d] trailer: %.*s: %.*s", stream->id, (int)namelen, name, (int)valuelen, value); result = Curl_dynhds_add(&stream->resp_trailers, (const char *)name, namelen, (const char *)value, valuelen); if(result) { - cf_h2_header_error(cf, data_s, stream, result); + cf_h2_header_error(cf, data, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; } @@ -1512,14 +1512,14 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, result = Curl_http_decode_status(&stream->status_code, (const char *)value, valuelen); if(result) { - cf_h2_header_error(cf, data_s, stream, result); + cf_h2_header_error(cf, data, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; } hlen = curl_msnprintf(buffer, sizeof(buffer), HTTP_PSEUDO_STATUS ":%d\r", stream->status_code); - result = Curl_headers_push(data_s, buffer, hlen, CURLH_PSEUDO); + result = Curl_headers_push(data, buffer, hlen, CURLH_PSEUDO); if(result) { - cf_h2_header_error(cf, data_s, stream, result); + cf_h2_header_error(cf, data, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; } curlx_dyn_reset(&ctx->scratch); @@ -1529,17 +1529,17 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(!result) result = curlx_dyn_addn(&ctx->scratch, STRCONST(" \r\n")); if(!result) - h2_xfer_write_resp_hd(cf, data_s, stream, curlx_dyn_ptr(&ctx->scratch), + h2_xfer_write_resp_hd(cf, data, stream, curlx_dyn_ptr(&ctx->scratch), curlx_dyn_len(&ctx->scratch), FALSE); if(result) { - cf_h2_header_error(cf, data_s, stream, result); + cf_h2_header_error(cf, data, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; } /* if we receive data for another handle, wake that up */ - if(CF_DATA_CURRENT(cf) != data_s) - Curl_multi_mark_dirty(data_s); + if(CF_DATA_CURRENT(cf) != data) + Curl_multi_mark_dirty(data); - CURL_TRC_CF(data_s, cf, "[%d] status: HTTP/2 %03d", + CURL_TRC_CF(data, cf, "[%d] status: HTTP/2 %03d", stream->id, stream->status_code); return 0; } @@ -1556,17 +1556,17 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(!result) result = curlx_dyn_addn(&ctx->scratch, STRCONST("\r\n")); if(!result) - h2_xfer_write_resp_hd(cf, data_s, stream, curlx_dyn_ptr(&ctx->scratch), + h2_xfer_write_resp_hd(cf, data, stream, curlx_dyn_ptr(&ctx->scratch), curlx_dyn_len(&ctx->scratch), FALSE); if(result) { - cf_h2_header_error(cf, data_s, stream, result); + cf_h2_header_error(cf, data, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; } /* if we receive data for another handle, wake that up */ - if(CF_DATA_CURRENT(cf) != data_s) - Curl_multi_mark_dirty(data_s); + if(CF_DATA_CURRENT(cf) != data) + Curl_multi_mark_dirty(data); - CURL_TRC_CF(data_s, cf, "[%d] header: %.*s: %.*s", + CURL_TRC_CF(data, cf, "[%d] header: %.*s: %.*s", stream->id, (int)namelen, name, (int)valuelen, value); return 0; /* 0 is successful */ @@ -2840,9 +2840,7 @@ bool Curl_http2_may_switch(struct Curl_easy *data) (data->state.http_neg.wanted & CURL_HTTP_V2x) && data->state.http_neg.h2_prior_knowledge) { #ifndef CURL_DISABLE_PROXY - if(data->conn->bits.httpproxy && !data->conn->bits.tunnel_proxy) { - /* We do not support HTTP/2 proxies yet. Also it is debatable - whether or not this setting should apply to HTTP/2 proxies. */ + if(data->conn->bits.origin_is_proxy) { infof(data, "Ignoring HTTP/2 prior knowledge due to proxy"); return FALSE; } diff --git a/lib/http_aws_sigv4.c b/lib/http_aws_sigv4.c index 7a61bfb729..780eed67b9 100644 --- a/lib/http_aws_sigv4.c +++ b/lib/http_aws_sigv4.c @@ -1165,12 +1165,11 @@ fail: CURLcode Curl_output_aws_sigv4(struct Curl_easy *data) { CURLcode result = CURLE_OUT_OF_MEMORY; - struct connectdata *conn = data->conn; struct Curl_str provider0 = { NULL, 0 }; struct Curl_str provider1 = { NULL, 0 }; struct Curl_str region = { NULL, 0 }; struct Curl_str service = { NULL, 0 }; - const char *hostname = conn->origin->hostname; + const char *hostname = data->state.origin->hostname; char timestamp[TIMESTAMP_SIZE]; char date[9]; struct dynbuf canonical_headers; diff --git a/lib/http_digest.c b/lib/http_digest.c index 640553867f..25783d0969 100644 --- a/lib/http_digest.c +++ b/lib/http_digest.c @@ -117,9 +117,9 @@ CURLcode Curl_output_digest(struct Curl_easy *data, #endif } else { - DEBUGASSERT(data->conn->origin); + DEBUGASSERT(data->state.origin); digest = &data->state.digest; - digest_flush_stale(digest, data->conn->origin, data->state.creds); + digest_flush_stale(digest, data->state.origin, data->state.creds); allocuserpwd = &data->req.hd_auth; creds = data->state.creds; authp = &data->state.authhost; diff --git a/lib/http_negotiate.c b/lib/http_negotiate.c index 5a05ab1412..4aa67f01d5 100644 --- a/lib/http_negotiate.c +++ b/lib/http_negotiate.c @@ -73,7 +73,7 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, } else { creds = data->state.creds; - host = conn->origin->hostname; + host = data->state.origin->hostname; state = conn->http_negotiate_state; } diff --git a/lib/http_ntlm.c b/lib/http_ntlm.c index 05c2f2faf8..1442fd6f7a 100644 --- a/lib/http_ntlm.c +++ b/lib/http_ntlm.c @@ -149,7 +149,7 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) else { allocuserpwd = &data->req.hd_auth; creds = data->state.creds; - hostname = conn->origin->hostname; + hostname = data->state.origin->hostname; state = &conn->http_ntlm_state; authp = &data->state.authhost; } diff --git a/lib/http_proxy.c b/lib/http_proxy.c index d214afee77..d019796b53 100644 --- a/lib/http_proxy.c +++ b/lib/http_proxy.c @@ -55,8 +55,7 @@ static CURLcode dynhds_add_custom(struct Curl_easy *data, else if(is_connect && is_udp) proxy = HEADER_CONNECT_UDP; else - proxy = (conn->bits.httpproxy && !conn->bits.tunnel_proxy) ? - HEADER_PROXY : HEADER_SERVER; + proxy = conn->bits.origin_is_proxy ? HEADER_PROXY : HEADER_SERVER; switch(proxy) { case HEADER_SERVER: diff --git a/lib/http_proxy.h b/lib/http_proxy.h index b60bad96f6..86c9088093 100644 --- a/lib/http_proxy.h +++ b/lib/http_proxy.h @@ -75,15 +75,8 @@ CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at, extern struct Curl_cftype Curl_cft_http_proxy; -#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ - -#define IS_HTTPS_PROXY(t) \ - (((t) == CURLPROXY_HTTPS) || \ - ((t) == CURLPROXY_HTTPS2) || \ - ((t) == CURLPROXY_HTTPS3)) - -#define IS_QUIC_PROXY(t) ((t) == CURLPROXY_HTTPS3) - uint8_t Curl_http_proxy_transport(uint8_t proxytype); +#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ + #endif /* HEADER_CURL_HTTP_PROXY_H */ diff --git a/lib/noproxy.c b/lib/noproxy.c deleted file mode 100644 index 05c59a0f1a..0000000000 --- a/lib/noproxy.c +++ /dev/null @@ -1,266 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , 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" - -#ifndef CURL_DISABLE_PROXY - -#include "curlx/inet_pton.h" -#include "noproxy.h" -#include "curlx/strparse.h" - -#ifdef HAVE_NETINET_IN_H -#include -#endif - -#ifdef HAVE_ARPA_INET_H -#include -#endif - -/* - * cidr4_match() returns TRUE if the given IPv4 address is within the - * specified CIDR address range. - * - * @unittest 1614 - */ -UNITTEST bool cidr4_match(const char *ipv4, /* 1.2.3.4 address */ - const char *network, /* 1.2.3.4 address */ - unsigned int bits); -UNITTEST bool cidr4_match(const char *ipv4, /* 1.2.3.4 address */ - const char *network, /* 1.2.3.4 address */ - unsigned int bits) -{ - unsigned int address = 0; - unsigned int check = 0; - - if(bits > 32) - /* strange input */ - return FALSE; - - if(curlx_inet_pton(AF_INET, ipv4, &address) != 1) - return FALSE; - if(curlx_inet_pton(AF_INET, network, &check) != 1) - return FALSE; - - if(bits && (bits != 32)) { - unsigned int mask = 0xffffffff << (32 - bits); - unsigned int haddr = htonl(address); - unsigned int hcheck = htonl(check); -#if 0 - curl_mfprintf(stderr, "Host %s (%x) network %s (%x) " - "bits %u mask %x => %x\n", - ipv4, haddr, network, hcheck, bits, mask, - (haddr ^ hcheck) & mask); -#endif - if((haddr ^ hcheck) & mask) - return FALSE; - return TRUE; - } - return address == check; -} - -/* @unittest 1614 */ -UNITTEST bool cidr6_match(const char *ipv6, const char *network, - unsigned int bits); -UNITTEST bool cidr6_match(const char *ipv6, const char *network, - unsigned int bits) -{ -#ifdef USE_IPV6 - unsigned int bytes; - unsigned int rest; - unsigned char address[16]; - unsigned char check[16]; - - if(!bits) - bits = 128; - - bytes = bits / 8; - rest = bits & 0x07; - if((bytes > 16) || ((bytes == 16) && rest)) - return FALSE; - if(curlx_inet_pton(AF_INET6, ipv6, address) != 1) - return FALSE; - if(curlx_inet_pton(AF_INET6, network, check) != 1) - return FALSE; - if(bytes && memcmp(address, check, bytes)) - return FALSE; - if(rest && ((address[bytes] ^ check[bytes]) & (0xff << (8 - rest)))) - return FALSE; - - return TRUE; -#else - (void)ipv6; - (void)network; - (void)bits; - return FALSE; -#endif -} - -enum nametype { - TYPE_HOST, - TYPE_IPV4, - TYPE_IPV6 -}; - -static bool match_host(const char *token, size_t tokenlen, - const char *name, size_t namelen) -{ - bool match = FALSE; - - /* ignore trailing dots in the token to check */ - if(token[tokenlen - 1] == '.') - tokenlen--; - - if(tokenlen && (*token == '.')) { - /* ignore leading token dot as well */ - token++; - tokenlen--; - } - /* A: example.com matches 'example.com' - B: www.example.com matches 'example.com' - C: nonexample.com DOES NOT match 'example.com' - */ - if(tokenlen == namelen) - /* case A, exact match */ - match = curl_strnequal(token, name, namelen); - else if(tokenlen < namelen) { - /* case B, tailmatch domain */ - match = (name[namelen - tokenlen - 1] == '.') && - curl_strnequal(token, name + (namelen - tokenlen), tokenlen); - } - /* case C passes through, not a match */ - return match; -} - -static bool match_ip(int type, const char *token, size_t tokenlen, - const char *name) -{ - char *slash; - unsigned int bits = 0; - char checkip[128]; - if(tokenlen >= sizeof(checkip)) - /* this cannot match */ - return FALSE; - /* copy the check name to a temp buffer */ - memcpy(checkip, token, tokenlen); - checkip[tokenlen] = 0; - - slash = strchr(checkip, '/'); - /* if the slash is part of this token, use it */ - if(slash) { - curl_off_t value; - const char *p = &slash[1]; - if(curlx_str_number(&p, &value, 128) || *p) - return FALSE; - /* a too large value is rejected in the cidr function below */ - bits = (unsigned int)value; - *slash = 0; /* null-terminate there */ - } - if(type == TYPE_IPV6) - return cidr6_match(name, checkip, bits); - else - return cidr4_match(name, checkip, bits); -} - -/**************************************************************** - * Checks if the host is in the noproxy list. returns TRUE if it matches and - * therefore the proxy should NOT be used. - ****************************************************************/ -bool Curl_check_noproxy(const char *name, const char *no_proxy) -{ - /* - * If we do not have a hostname at all, like for example with a FILE - * transfer, we have nothing to interrogate the noproxy list with. - */ - if(!name || name[0] == '\0') - return FALSE; - - /* no_proxy=domain1.dom,host.domain2.dom - * (a comma-separated list of hosts which should - * not be proxied, or an asterisk to override - * all proxy variables) - */ - if(no_proxy && no_proxy[0]) { - const char *p = no_proxy; - size_t namelen; - char address[16]; - enum nametype type = TYPE_HOST; - if(!strcmp("*", no_proxy)) - return TRUE; - - /* NO_PROXY was specified and it was not only an asterisk */ - - /* Check if name is an IP address; if not, assume it being a hostname. */ - namelen = strlen(name); - if(curlx_inet_pton(AF_INET, name, &address) == 1) - type = TYPE_IPV4; -#ifdef USE_IPV6 - else if(curlx_inet_pton(AF_INET6, name, &address) == 1) - type = TYPE_IPV6; -#endif - else { - /* ignore trailing dots in the hostname */ - if(name[namelen - 1] == '.') - namelen--; - } - - while(*p) { - const char *token; - size_t tokenlen = 0; - - /* pass blanks */ - curlx_str_passblanks(&p); - - token = p; - /* pass over the pattern */ - while(*p && !ISBLANK(*p) && (*p != ',')) { - p++; - tokenlen++; - } - - if(tokenlen) { - bool match = FALSE; - if(type == TYPE_HOST) - match = match_host(token, tokenlen, name, namelen); - else - match = match_ip(type, token, tokenlen, name); - - if(match) - return TRUE; - } - - /* pass blanks after pattern */ - curlx_str_passblanks(&p); - /* if not a comma, this ends the loop */ - if(*p != ',') - break; - /* pass any number of commas */ - while(*p == ',') - p++; - } /* while(*p) */ - } /* NO_PROXY was specified and it was not only an asterisk */ - - return FALSE; -} - -#endif /* CURL_DISABLE_PROXY */ diff --git a/lib/peer.c b/lib/peer.c index a1ed2251eb..aa3ac5244a 100644 --- a/lib/peer.c +++ b/lib/peer.c @@ -630,7 +630,7 @@ CURLcode Curl_peer_from_proxy_url(CURLU *uh, } DEBUGASSERT(pp.scheme); - if(IS_HTTPS_PROXY(proxytype) && + if(CURL_PROXY_IS_HTTPS(proxytype) && !Curl_ssl_supports(data, SSLSUPP_HTTPS_PROXY)) { failf(data, "Unsupported proxy \'%s\', libcurl is built without the " "HTTPS-proxy support.", url); diff --git a/lib/proxy.c b/lib/proxy.c new file mode 100644 index 0000000000..afd313a107 --- /dev/null +++ b/lib/proxy.c @@ -0,0 +1,673 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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" + +#ifndef CURL_DISABLE_PROXY + +#include "urldata.h" +#include "curl_trc.h" +#include "protocol.h" +#include "proxy.h" +#include "http_proxy.h" +#include "strcase.h" +#include "url.h" +#include "vauth/vauth.h" +#include "curlx/inet_pton.h" +#include "curlx/strparse.h" + +#ifdef HAVE_NETINET_IN_H +#include +#endif + +#ifdef HAVE_ARPA_INET_H +#include +#endif + +/* + * cidr4_match() returns TRUE if the given IPv4 address is within the + * specified CIDR address range. + * + * @unittest 1614 + */ +UNITTEST bool cidr4_match(const char *ipv4, /* 1.2.3.4 address */ + const char *network, /* 1.2.3.4 address */ + unsigned int bits); +UNITTEST bool cidr4_match(const char *ipv4, /* 1.2.3.4 address */ + const char *network, /* 1.2.3.4 address */ + unsigned int bits) +{ + unsigned int address = 0; + unsigned int check = 0; + + if(bits > 32) + /* strange input */ + return FALSE; + + if(curlx_inet_pton(AF_INET, ipv4, &address) != 1) + return FALSE; + if(curlx_inet_pton(AF_INET, network, &check) != 1) + return FALSE; + + if(bits && (bits != 32)) { + unsigned int mask = 0xffffffff << (32 - bits); + unsigned int haddr = htonl(address); + unsigned int hcheck = htonl(check); +#if 0 + curl_mfprintf(stderr, "Host %s (%x) network %s (%x) " + "bits %u mask %x => %x\n", + ipv4, haddr, network, hcheck, bits, mask, + (haddr ^ hcheck) & mask); +#endif + if((haddr ^ hcheck) & mask) + return FALSE; + return TRUE; + } + return address == check; +} + +/* @unittest 1614 */ +UNITTEST bool cidr6_match(const char *ipv6, const char *network, + unsigned int bits); +UNITTEST bool cidr6_match(const char *ipv6, const char *network, + unsigned int bits) +{ +#ifdef USE_IPV6 + unsigned int bytes; + unsigned int rest; + unsigned char address[16]; + unsigned char check[16]; + + if(!bits) + bits = 128; + + bytes = bits / 8; + rest = bits & 0x07; + if((bytes > 16) || ((bytes == 16) && rest)) + return FALSE; + if(curlx_inet_pton(AF_INET6, ipv6, address) != 1) + return FALSE; + if(curlx_inet_pton(AF_INET6, network, check) != 1) + return FALSE; + if(bytes && memcmp(address, check, bytes)) + return FALSE; + if(rest && ((address[bytes] ^ check[bytes]) & (0xff << (8 - rest)))) + return FALSE; + + return TRUE; +#else + (void)ipv6; + (void)network; + (void)bits; + return FALSE; +#endif +} + +enum nametype { + TYPE_HOST, + TYPE_IPV4, + TYPE_IPV6 +}; + +static bool match_host(const char *token, size_t tokenlen, + const char *name, size_t namelen) +{ + bool match = FALSE; + + /* ignore trailing dots in the token to check */ + if(token[tokenlen - 1] == '.') + tokenlen--; + + if(tokenlen && (*token == '.')) { + /* ignore leading token dot as well */ + token++; + tokenlen--; + } + /* A: example.com matches 'example.com' + B: www.example.com matches 'example.com' + C: nonexample.com DOES NOT match 'example.com' + */ + if(tokenlen == namelen) + /* case A, exact match */ + match = curl_strnequal(token, name, namelen); + else if(tokenlen < namelen) { + /* case B, tailmatch domain */ + match = (name[namelen - tokenlen - 1] == '.') && + curl_strnequal(token, name + (namelen - tokenlen), tokenlen); + } + /* case C passes through, not a match */ + return match; +} + +static bool match_ip(int type, const char *token, size_t tokenlen, + const char *name) +{ + char *slash; + unsigned int bits = 0; + char checkip[128]; + if(tokenlen >= sizeof(checkip)) + /* this cannot match */ + return FALSE; + /* copy the check name to a temp buffer */ + memcpy(checkip, token, tokenlen); + checkip[tokenlen] = 0; + + slash = strchr(checkip, '/'); + /* if the slash is part of this token, use it */ + if(slash) { + curl_off_t value; + const char *p = &slash[1]; + if(curlx_str_number(&p, &value, 128) || *p) + return FALSE; + /* a too large value is rejected in the cidr function below */ + bits = (unsigned int)value; + *slash = 0; /* null-terminate there */ + } + if(type == TYPE_IPV6) + return cidr6_match(name, checkip, bits); + else + return cidr4_match(name, checkip, bits); +} + +/**************************************************************** + * Checks if the host is in the noproxy list. returns TRUE if it matches and + * therefore the proxy should NOT be used. + ****************************************************************/ +/* @unittest 1614 */ +UNITTEST bool proxy_check_noproxy(const char *name, const char *no_proxy); +UNITTEST bool proxy_check_noproxy(const char *name, const char *no_proxy) +{ + /* + * If we do not have a hostname at all, like for example with a FILE + * transfer, we have nothing to interrogate the noproxy list with. + */ + if(!name || name[0] == '\0') + return FALSE; + + /* no_proxy=domain1.dom,host.domain2.dom + * (a comma-separated list of hosts which should + * not be proxied, or an asterisk to override + * all proxy variables) + */ + if(no_proxy && no_proxy[0]) { + const char *p = no_proxy; + size_t namelen; + char address[16]; + enum nametype type = TYPE_HOST; + if(!strcmp("*", no_proxy)) + return TRUE; + + /* NO_PROXY was specified and it was not only an asterisk */ + + /* Check if name is an IP address; if not, assume it being a hostname. */ + namelen = strlen(name); + if(curlx_inet_pton(AF_INET, name, &address) == 1) + type = TYPE_IPV4; +#ifdef USE_IPV6 + else if(curlx_inet_pton(AF_INET6, name, &address) == 1) + type = TYPE_IPV6; +#endif + else { + /* ignore trailing dots in the hostname */ + if(name[namelen - 1] == '.') + namelen--; + } + + while(*p) { + const char *token; + size_t tokenlen = 0; + + /* pass blanks */ + curlx_str_passblanks(&p); + + token = p; + /* pass over the pattern */ + while(*p && !ISBLANK(*p) && (*p != ',')) { + p++; + tokenlen++; + } + + if(tokenlen) { + bool match = FALSE; + if(type == TYPE_HOST) + match = match_host(token, tokenlen, name, namelen); + else + match = match_ip(type, token, tokenlen, name); + + if(match) + return TRUE; + } + + /* pass blanks after pattern */ + curlx_str_passblanks(&p); + /* if not a comma, this ends the loop */ + if(*p != ',') + break; + /* pass any number of commas */ + while(*p == ',') + p++; + } /* while(*p) */ + } /* NO_PROXY was specified and it was not only an asterisk */ + + return FALSE; +} + +#ifndef CURL_DISABLE_HTTP + +/**************************************************************** + * Detect what (if any) proxy to use. Remember that this selects a host + * name and is not limited to HTTP proxies only. + * The returned pointer must be freed by the caller. + ****************************************************************/ +static char *proxy_detect_proxy(struct Curl_easy *data, + const struct Curl_scheme *scheme) +{ + char *proxy = NULL; + + /* If proxy was not specified, we check for default proxy environment + * variables, to enable i.e Lynx compliance: + * + * http_proxy=http://some.server.dom:port/ + * https_proxy=http://some.server.dom:port/ + * ftp_proxy=http://some.server.dom:port/ + * no_proxy=domain1.dom,host.domain2.dom + * (a comma-separated list of hosts which should + * not be proxied, or an asterisk to override + * all proxy variables) + * all_proxy=http://some.server.dom:port/ + * (seems to exist for the CERN www lib. Probably + * the first to check for.) + * + * For compatibility, the all-uppercase versions of these variables are + * checked if the lowercase versions do not exist. + */ + char proxy_env[20]; + const char *envp; + VERBOSE(envp = proxy_env); + + curl_msnprintf(proxy_env, sizeof(proxy_env), "%s_proxy", scheme->name); + + /* read the protocol proxy: */ + proxy = curl_getenv(proxy_env); + + /* + * We do not try the uppercase version of HTTP_PROXY because of + * security reasons: + * + * When curl is used in a webserver application + * environment (cgi or php), this environment variable can + * be controlled by the web server user by setting the + * http header 'Proxy:' to some value. + * + * This can cause 'internal' http/ftp requests to be + * arbitrarily redirected by any external attacker. + */ + if(!proxy && !curl_strequal("http_proxy", proxy_env)) { + /* There was no lowercase variable, try the uppercase version: */ + Curl_strntoupper(proxy_env, proxy_env, sizeof(proxy_env)); + proxy = curl_getenv(proxy_env); + } + + if(!proxy) { +#ifndef CURL_DISABLE_WEBSOCKETS + /* websocket proxy fallbacks */ + 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); /* default proxy to use */ + 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 /* CURL_DISABLE_HTTP */ + +/* + * If this is supposed to use a proxy, we need to figure out the proxy + * hostname, so that we can reuse an existing connection + * that may exist registered to the same proxy host. + */ +static CURLcode parse_proxy(struct Curl_easy *data, + const char *proxy, + bool for_pre_proxy, + struct proxy_info *proxyinfo) +{ + char *proxyuser = NULL; + char *proxypasswd = NULL; + char *scheme = NULL; + CURLcode result = CURLE_OK; + /* Set the start proxy type for url scheme guessing */ + uint8_t proxytype = for_pre_proxy ? CURLPROXY_SOCKS4 : data->set.proxytype; + CURLU *uhp = curl_url(); + CURLUcode uc; + + if(!uhp) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + /* When parsing the proxy, allowing non-supported schemes since we have + these made up ones for proxies. Guess scheme for URLs without it. */ + uc = curl_url_set(uhp, CURLUPART_URL, proxy, + CURLU_NON_SUPPORT_SCHEME | CURLU_GUESS_SCHEME); + if(!uc) { + /* parsed okay as a URL - only update proxytype when scheme was explicit */ + uc = curl_url_get(uhp, CURLUPART_SCHEME, &scheme, CURLU_NO_GUESS_SCHEME); + if(!uc) { + result = Curl_scheme_to_proxytype(data, scheme, &proxytype, proxy); + if(result) + goto error; + } + else if(uc != CURLUE_NO_SCHEME) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + /* else: no explicit scheme, keep the configured proxytype */ + } + else { + failf(data, "Unsupported proxy syntax in \'%s\': %s", proxy, + curl_url_strerror(uc)); + result = CURLE_COULDNT_RESOLVE_PROXY; + goto error; + } + + result = Curl_peer_from_proxy_url(uhp, data, proxy, proxytype, + &proxyinfo->peer, &proxytype); + if(result) + goto error; + + switch(proxytype) { + case CURLPROXY_HTTP: + case CURLPROXY_HTTP_1_0: + case CURLPROXY_HTTPS: + case CURLPROXY_HTTPS2: + case CURLPROXY_HTTPS3: + if(for_pre_proxy) { + failf(data, "Unsupported pre-proxy type for \'%s\'", proxy); + result = CURLE_COULDNT_RESOLVE_PROXY; + goto error; + } + break; + case CURLPROXY_SOCKS4: + case CURLPROXY_SOCKS4A: + case CURLPROXY_SOCKS5: + case CURLPROXY_SOCKS5_HOSTNAME: + break; + default: + failf(data, "Unsupported proxy type %u for \'%s\'", proxytype, proxy); + result = CURLE_COULDNT_RESOLVE_PROXY; + goto error; + } + + /* Is there a username and password given in this proxy URL? */ + uc = curl_url_get(uhp, CURLUPART_USER, &proxyuser, CURLU_URLDECODE); + if(uc && (uc != CURLUE_NO_USER)) { + result = Curl_uc_to_curlcode(uc); + goto error; + } + uc = curl_url_get(uhp, CURLUPART_PASSWORD, &proxypasswd, CURLU_URLDECODE); + if(uc && (uc != CURLUE_NO_PASSWORD)) { + result = Curl_uc_to_curlcode(uc); + goto error; + } + + if(proxyuser || proxypasswd) { + result = Curl_creds_create(proxyuser, proxypasswd, NULL, NULL, + data->set.str[STRING_PROXY_SERVICE_NAME], + CREDS_URL, &proxyinfo->creds); + if(result) + goto error; + } + else if(!for_pre_proxy && + (data->set.str[STRING_PROXYUSERNAME] || + data->set.str[STRING_PROXYPASSWORD] || + data->set.str[STRING_PROXY_SERVICE_NAME])) { + /* No user/passwd in URL, if this is not a pre-proxy, the + * CURLOPT_PROXY* settings apply. */ + result = Curl_creds_create(data->set.str[STRING_PROXYUSERNAME], + data->set.str[STRING_PROXYPASSWORD], + NULL, NULL, + data->set.str[STRING_PROXY_SERVICE_NAME], + CREDS_OPTION, &proxyinfo->creds); + } + else + Curl_creds_unlink(&proxyinfo->creds); + + proxyinfo->proxytype = proxytype; + +error: + curlx_free(scheme); + curlx_free(proxyuser); + curlx_free(proxypasswd); + curl_url_cleanup(uhp); +#ifdef DEBUGBUILD + if(!result) { + DEBUGASSERT(proxyinfo); + DEBUGASSERT(proxyinfo->peer); + } +#endif + return result; +} + +/* Is transfer's origin exempted from proxy use? */ +static bool proxy_do_not_proxy(struct Curl_easy *data) +{ + const char *no_proxy; + char *env_no_proxy = NULL; + bool do_not_proxy; + + /* no proxying if the transfer does not use the network */ + if(data->state.origin->scheme->flags & PROTOPT_NONETWORK) + return TRUE; + + no_proxy = data->set.str[STRING_NOPROXY]; + if(!no_proxy) { + const char *p = "no_proxy"; + env_no_proxy = curl_getenv(p); + if(!env_no_proxy) { + p = "NO_PROXY"; + env_no_proxy = curl_getenv(p); + } + if(env_no_proxy) + infof(data, "Uses proxy env variable %s == '%s'", p, env_no_proxy); + no_proxy = env_no_proxy; + } + + do_not_proxy = proxy_check_noproxy(data->state.origin->hostname, no_proxy); + curlx_safefree(env_no_proxy); + return do_not_proxy; +} + +CURLcode Curl_proxy_init_conn(struct Curl_easy *data, + struct connectdata *conn) +{ + char *proxy = NULL; + char *pre_proxy = NULL; + bool do_env_detect = TRUE; + CURLcode result = CURLE_OK; + + /* Enforce no proxy use unless we decide to use one */ + conn->bits.origin_is_proxy = FALSE; + DEBUGASSERT(!conn->socks_proxy.peer); + DEBUGASSERT(!conn->http_proxy.peer); + + if(proxy_do_not_proxy(data)) + goto out; + + /************************************************************* + * Detect what (if any) proxy to use + *************************************************************/ + /* the empty config strings disable proxy use and env detects */ + if(data->set.str[STRING_PROXY]) { + if(*data->set.str[STRING_PROXY]) { + proxy = curlx_strdup(data->set.str[STRING_PROXY]); + /* if global proxy is set, this is it */ + if(!proxy) { + failf(data, "memory shortage"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + else + do_env_detect = FALSE; + } + + if(data->set.str[STRING_PRE_PROXY]) { + if(*data->set.str[STRING_PRE_PROXY]) { + pre_proxy = curlx_strdup(data->set.str[STRING_PRE_PROXY]); + /* if global socks proxy is set, this is it */ + if(!pre_proxy) { + failf(data, "memory shortage"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + else + do_env_detect = FALSE; + } + +#ifndef CURL_DISABLE_HTTP + /* None configured, detect possible proxy from environment. */ + if(!proxy && !pre_proxy && do_env_detect) + proxy = proxy_detect_proxy(data, conn->scheme); +#else + (void)do_env_detect; +#endif /* CURL_DISABLE_HTTP */ + + if(!proxy && !pre_proxy) + goto out; + + if(pre_proxy) { + result = parse_proxy(data, pre_proxy, TRUE, &conn->socks_proxy); + if(result) + goto out; + } + + if(proxy) { + result = parse_proxy(data, proxy, FALSE, &conn->http_proxy); + if(result) + goto out; + + switch(conn->http_proxy.proxytype) { + case CURLPROXY_SOCKS4: + case CURLPROXY_SOCKS4A: + case CURLPROXY_SOCKS5: + case CURLPROXY_SOCKS5_HOSTNAME: + /* Whoops, it's not a HTTP proxy */ + if(pre_proxy) { + /* and we already have a SOCKS pre-proxy. Cannot have both */ + failf(data, "Having a SOCKS pre-proxy and proxy is not " + "supported with \'%s\'", proxy); + result = CURLE_COULDNT_RESOLVE_PROXY; + goto out; + } + /* switch */ + conn->socks_proxy = conn->http_proxy; + memset(&conn->http_proxy, 0, sizeof(conn->http_proxy)); + break; + default: + /* all other types are HTTP */ + break; + } + } + + if(conn->socks_proxy.peer) { + DEBUGASSERT(!CURL_PROXY_IS_ANY_HTTP(conn->socks_proxy.proxytype)); + } + +#ifdef CURL_DISABLE_HTTP + if(conn->http_proxy.peer) { + /* asking for an HTTP proxy is a bit funny when HTTP is disabled... */ + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; + } + +#else /* CURL_DISABLE_HTTP */ + if(conn->http_proxy.peer) { + const struct Curl_scheme *scheme = data->state.origin->scheme; + bool tunnel_proxy = (bool)data->set.tunnel_thru_httpproxy; + DEBUGASSERT(CURL_PROXY_IS_ANY_HTTP(conn->http_proxy.proxytype)); + + if(!tunnel_proxy) { + /* Decide if we tunnel through proxy automatically */ + if(conn->via_peer) { + /* With connect-to, we always tunnel */ + tunnel_proxy = TRUE; + } + else if(scheme->flags & PROTOPT_SSL) { + /* If the transfer is supposed to be secure, we tunnel */ + tunnel_proxy = TRUE; + } + else if(scheme->flags & PROTOPT_HTTP_PROXY_TUNNEL) { + /* transfer scheme required tunneling */ + tunnel_proxy = TRUE; + } + else if(!(scheme->protocol & PROTO_FAMILY_HTTP) && + !(scheme->flags & PROTOPT_PROXY_AS_HTTP)) { + /* Cannot delegate transfer URL to HTTP proxy */ + tunnel_proxy = TRUE; + } + } + + if(!tunnel_proxy) { + /* HTTP proxy used in forwarding mode. This means the connection + * is really to the proxy and NOT the origin of the transfer. */ + DEBUGASSERT(!conn->via_peer); + Curl_peer_link(&conn->origin, conn->http_proxy.peer); + conn->scheme = conn->http_proxy.peer->scheme; + conn->bits.origin_is_proxy = TRUE; + } + +#ifndef CURL_DISABLE_DIGEST_AUTH + if(!Curl_safecmp(data->state.envproxy, proxy)) { + /* proxy changed */ + Curl_auth_digest_cleanup(&data->state.proxydigest); + curlx_free(data->state.envproxy); + data->state.envproxy = curlx_strdup(proxy); + } +#endif + } +#endif /* !CURL_DISABLE_HTTP */ + +out: + curlx_free(pre_proxy); + curlx_free(proxy); + return result; +} + +#endif /* CURL_DISABLE_PROXY */ diff --git a/lib/noproxy.h b/lib/proxy.h similarity index 56% rename from lib/noproxy.h rename to lib/proxy.h index e16c139bb5..7962f612c5 100644 --- a/lib/noproxy.h +++ b/lib/proxy.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_NOPROXY_H -#define HEADER_CURL_NOPROXY_H +#ifndef HEADER_CURL_PROXY_H +#define HEADER_CURL_PROXY_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -26,7 +26,34 @@ #include "curl_setup.h" #ifndef CURL_DISABLE_PROXY -bool Curl_check_noproxy(const char *name, const char *no_proxy); -#endif -#endif /* HEADER_CURL_NOPROXY_H */ +struct Curl_easy; +struct Curl_peer; +struct Curl_creds; +struct connectdata; + +struct proxy_info { + struct Curl_peer *peer; /* proxy to this peer */ + struct Curl_creds *creds; /* use these credentials, maybe NULL */ + uint8_t proxytype; /* what kind of proxy that is in use */ +}; + +#define CURL_PROXY_IS_HTTPS(t) \ + (((t) == CURLPROXY_HTTPS) || \ + ((t) == CURLPROXY_HTTPS2) || \ + ((t) == CURLPROXY_HTTPS3)) + +#define CURL_PROXY_IS_HTTP(t) \ + (((t) == CURLPROXY_HTTP) || \ + ((t) == CURLPROXY_HTTP_1_0)) + +#define CURL_PROXY_IS_ANY_HTTP(t) \ + (CURL_PROXY_IS_HTTP(t) || \ + CURL_PROXY_IS_HTTPS(t)) + +CURLcode Curl_proxy_init_conn(struct Curl_easy *data, + struct connectdata *conn); + +#endif /* !CURL_DISABLE_PROXY */ + +#endif /* HEADER_CURL_PROXY_H */ diff --git a/lib/transfer.c b/lib/transfer.c index edb6d62cbc..d3b5eb4eca 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -493,6 +493,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) /* initial transfer request coming up, forget the initial origin * from a previous perform() on this handle. */ Curl_peer_unlink(&data->state.initial_origin); + Curl_peer_unlink(&data->state.origin); data->state.requests = 0; data->state.followlocation = 0; /* reset the location-follow counter */ data->state.this_is_a_follow = FALSE; /* reset this */ diff --git a/lib/url.c b/lib/url.c index 73dc7f509c..8da2aca924 100644 --- a/lib/url.c +++ b/lib/url.c @@ -86,7 +86,7 @@ #include "urlapi-int.h" #include "system_win32.h" #include "hsts.h" -#include "noproxy.h" +#include "proxy.h" #include "cfilters.h" #include "idn.h" #include "http_proxy.h" @@ -245,6 +245,7 @@ CURLcode Curl_close(struct Curl_easy **datap) /* Close down all open SSL info and sessions */ Curl_ssl_close_all(data); + Curl_peer_unlink(&data->state.origin); Curl_peer_unlink(&data->state.initial_origin); Curl_ssl_free_certinfo(data); @@ -840,36 +841,27 @@ static bool url_match_ssl_use(struct connectdata *conn, 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) + if(m->needle->bits.origin_is_proxy != conn->bits.origin_is_proxy) return FALSE; - if(m->needle->bits.socksproxy && - !proxy_info_matches(&m->needle->socks_proxy, &conn->socks_proxy)) + if(!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(!proxy_info_matches(&m->needle->http_proxy, &conn->http_proxy)) + if(CURL_PROXY_IS_HTTPS(m->needle->http_proxy.proxytype)) { + /* https proxies come in different types, http/1.1, h2, ... */ + /* match SSL config to proxy */ + 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; - - if(IS_HTTPS_PROXY(m->needle->http_proxy.proxytype)) { - /* https proxies come in different types, http/1.1, h2, ... */ - if(m->needle->http_proxy.proxytype != conn->http_proxy.proxytype) - return FALSE; - /* match SSL config to proxy */ - 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; - } - /* the SSL config to the server, which may apply here is checked - * further below */ } + /* the SSL config to the server, which may apply here is checked + * further below */ } return TRUE; } @@ -981,14 +973,6 @@ static bool url_match_destination(struct connectdata *conn, if(!Curl_peer_same_destination(m->needle->via_peer, conn->via_peer)) return FALSE; -#ifndef CURL_DISABLE_PROXY - if(m->needle->bits.httpproxy && !m->needle->bits.tunnel_proxy) { - /* Talking to a non-tunneling HTTP proxy matches on proxy peers. */ - return Curl_peer_equal(m->needle->http_proxy.peer, - conn->http_proxy.peer); - } -#endif - if(m->needle->origin->scheme != conn->origin->scheme) { /* `needle` and `conn` not having the same scheme. * This is allowed for the same family *if* conn is using TLS. @@ -1310,28 +1294,6 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) /* Store current time to give a baseline to keepalive connection times. */ conn->keepalive = conn->created; -#ifndef CURL_DISABLE_PROXY - conn->http_proxy.proxytype = data->set.proxytype; - conn->socks_proxy.proxytype = CURLPROXY_SOCKS4; - - /* note that these two proxy bits are set on what looks to be - requested, they may be altered down the road */ - 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.tunnel_proxy = data->set.tunnel_thru_httpproxy; -#endif /* CURL_DISABLE_PROXY */ - #ifndef CURL_DISABLE_FTP conn->bits.ftp_use_epsv = data->set.ftp_use_epsv; conn->bits.ftp_use_eprt = data->set.ftp_use_eprt; @@ -1406,14 +1368,13 @@ CURLcode Curl_uc_to_curlcode(CURLUcode uc) #ifndef CURL_DISABLE_HSTS static CURLcode hsts_upgrade(struct Curl_easy *data, - struct connectdata *conn, CURLU *uh, uint16_t port_override, uint32_t scope_id) { /* HSTS upgrade */ - if(data->hsts && (conn->origin->scheme == &Curl_scheme_http) && - Curl_hsts_applies(data->hsts, conn->origin)) { + if(data->hsts && (data->state.origin->scheme == &Curl_scheme_http) && + Curl_hsts_applies(data->hsts, data->state.origin)) { char *url; CURLUcode uc; CURLcode result; @@ -1430,7 +1391,7 @@ static CURLcode hsts_upgrade(struct Curl_easy *data, Curl_bufref_set(&data->state.url, url, 0, curl_free); result = Curl_peer_from_url(uh, data, port_override, scope_id, - &data->state.up, &conn->origin); + &data->state.up, &data->state.origin); if(result) return result; infof(data, "Switched from HTTP to HTTPS due to HSTS => %s", url); @@ -1438,7 +1399,7 @@ static CURLcode hsts_upgrade(struct Curl_easy *data, return CURLE_OK; } #else -#define hsts_upgrade(x, y, z, a, b) CURLE_OK +#define hsts_upgrade(x, y, z, a) CURLE_OK #endif #ifndef CURL_DISABLE_NETRC @@ -1460,7 +1421,6 @@ static bool str_has_ctrl(const char *input) * option or a .netrc file, if applicable. */ static CURLcode url_set_data_creds_netrc(struct Curl_easy *data, - struct connectdata *conn, struct Curl_creds **pcreds) { struct Curl_creds *ncreds_out = NULL; @@ -1500,7 +1460,7 @@ static CURLcode url_set_data_creds_netrc(struct Curl_easy *data, goto out; ret = Curl_netrc_scan(data, &data->state.netrc, - conn->origin->hostname, + data->state.origin->hostname, Curl_creds_user(ncreds_in), data->set.str[STRING_NETRC_FILE], &ncreds_out); @@ -1512,7 +1472,7 @@ static CURLcode url_set_data_creds_netrc(struct Curl_easy *data, else if(ret && ((ret == NETRC_NO_MATCH) || (data->set.use_netrc == CURL_NETRC_OPTIONAL))) { infof(data, "Could not find host %s in the %s file; using defaults", - conn->origin->hostname, + data->state.origin->hostname, (data->set.str[STRING_NETRC_FILE] ? data->set.str[STRING_NETRC_FILE] : ".netrc")); } @@ -1523,7 +1483,7 @@ static CURLcode url_set_data_creds_netrc(struct Curl_easy *data, goto out; } else if(ncreds_out) { - if(!(conn->scheme->flags & PROTOPT_USERPWDCTRL)) { + if(!(data->state.origin->scheme->flags & PROTOPT_USERPWDCTRL)) { /* if the protocol cannot handle control codes in credentials, make sure there are none */ if(str_has_ctrl(ncreds_out->user) || @@ -1534,7 +1494,7 @@ static CURLcode url_set_data_creds_netrc(struct Curl_easy *data, } } CURL_TRC_M(data, "netrc: using credentials for %s as %s", - conn->origin->hostname, ncreds_out->user); + data->state.origin->hostname, ncreds_out->user); result = Curl_creds_merge(ncreds_out->user, ncreds_out->passwd, *pcreds, CREDS_NETRC, pcreds); if(result) @@ -1553,15 +1513,17 @@ static CURLcode url_set_data_creds_netrc(struct Curl_easy *data, DEBUGASSERT(0); } +#ifdef CURLVERBOSE + Curl_creds_trace(data, data->state.creds, "transfer credentials"); +#endif + out: Curl_creds_unlink(&ncreds_out); return result; } #endif /* CURL_DISABLE_NETRC */ -static CURLcode url_set_data_creds(struct Curl_easy *data, - struct connectdata *conn, - CURLU *uh) +static CURLcode url_set_data_creds(struct Curl_easy *data, CURLU *uh) { struct Curl_creds *newcreds = NULL; CURLcode result = CURLE_OK; @@ -1571,7 +1533,7 @@ static CURLcode url_set_data_creds(struct Curl_easy *data, data->set.str[STRING_BEARER] || data->set.str[STRING_SASL_AUTHZID] || data->set.str[STRING_SERVICE_NAME]) && - Curl_auth_allowed_to_origin(data, conn->origin)) { + Curl_auth_allowed_to_origin(data, data->state.origin)) { result = Curl_creds_create(data->set.str[STRING_USERNAME], data->set.str[STRING_PASSWORD], data->set.str[STRING_BEARER], @@ -1599,12 +1561,14 @@ static CURLcode url_set_data_creds(struct Curl_easy *data, } if(!result && data->state.up.user) { result = Curl_urldecode(data->state.up.user, 0, &udecoded, NULL, - conn->scheme->flags&PROTOPT_USERPWDCTRL ? + (data->state.origin->scheme->flags & + PROTOPT_USERPWDCTRL) ? REJECT_ZERO : REJECT_CTRL); } if(!result && data->state.up.password) { result = Curl_urldecode(data->state.up.password, 0, &pdecoded, NULL, - conn->scheme->flags&PROTOPT_USERPWDCTRL ? + (data->state.origin->scheme->flags & + PROTOPT_USERPWDCTRL) ? REJECT_ZERO : REJECT_CTRL); } if(!result) @@ -1622,7 +1586,7 @@ static CURLcode url_set_data_creds(struct Curl_easy *data, #ifndef CURL_DISABLE_NETRC /* Check for overridden login details and set them accordingly so that they are known when protocol->setup_connection is called! */ - result = url_set_data_creds_netrc(data, conn, &newcreds); + result = url_set_data_creds_netrc(data, &newcreds); #endif /* CURL_DISABLE_NETRC */ out: @@ -1634,139 +1598,37 @@ out: return result; } -/* - * Parse URL and fill in the relevant members of the connection struct. - */ -static CURLcode parseurlandfillconn(struct Curl_easy *data, - struct connectdata *conn) +static CURLcode url_set_conn_origin_etc(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result = CURLE_OK; - CURLU *uh; - CURLUcode uc; - bool use_set_uh = (data->set.uh && !data->state.this_is_a_follow); - uint16_t port_override = data->state.allow_port ? data->set.use_port : 0; - uint32_t scope_id = 0; - up_free(data); /* cleanup previous leftovers first */ + Curl_peer_link(&conn->origin, data->state.origin); - /* parse the URL */ - if(use_set_uh) - uh = data->state.uh = curl_url_dup(data->set.uh); - else - uh = data->state.uh = curl_url(); - if(!uh) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - /* Calculate the *real* URL this transfer uses, applying defaults - * where information is missing. */ - if(data->set.str[STRING_DEFAULT_PROTOCOL] && - !Curl_is_absolute_url(Curl_bufref_ptr(&data->state.url), NULL, 0, TRUE)) { - char *url = curl_maprintf("%s://%s", - data->set.str[STRING_DEFAULT_PROTOCOL], - Curl_bufref_ptr(&data->state.url)); - if(!url) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - Curl_bufref_set(&data->state.url, url, 0, curl_free); - } - - if(!use_set_uh) { - char *newurl; - uc = curl_url_set(uh, CURLUPART_URL, Curl_bufref_ptr(&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)); - result = Curl_uc_to_curlcode(uc); - goto out; - } - - /* after it was parsed, get the generated normalized version */ - uc = curl_url_get(uh, CURLUPART_URL, &newurl, CURLU_GET_EMPTY); - if(uc) { - result = Curl_uc_to_curlcode(uc); - goto out; - } - Curl_bufref_set(&data->state.url, newurl, 0, curl_free); - } - -#ifdef USE_IPV6 - scope_id = data->set.scope_id; -#endif - - /* `uh` is now as the connection should use it, probably. */ - result = Curl_peer_from_url(uh, data, port_override, scope_id, - &data->state.up, &conn->origin); - if(result) - goto out; - - result = hsts_upgrade(data, conn, uh, port_override, scope_id); - if(result) - goto out; - - /* now that the origin is fixed, check and set the connection scheme */ + /* set the connection scheme */ result = url_set_conn_scheme(data, conn, conn->origin->scheme); if(result) goto out; - /* When the transfers initial_origin is not set, this is the initial - * request. Remember this starting point. This is used to - * select credentials. */ - if(!data->state.initial_origin) - Curl_peer_link(&data->state.initial_origin, conn->origin); - - result = url_set_data_creds(data, conn, uh); - if(result) - goto out; - - uc = curl_url_get(uh, CURLUPART_OPTIONS, &data->state.up.options, - CURLU_URLDECODE); - if(!uc) { - conn->options = curlx_strdup(data->state.up.options); - if(!conn->options) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - } - else if(uc != CURLUE_NO_OPTIONS) { - result = Curl_uc_to_curlcode(uc); - goto out; - } - - uc = curl_url_get(uh, CURLUPART_PATH, &data->state.up.path, CURLU_URLENCODE); - if(uc) { - result = Curl_uc_to_curlcode(uc); - goto out; - } - - uc = curl_url_get(uh, CURLUPART_QUERY, &data->state.up.query, - CURLU_GET_EMPTY); - if(uc && (uc != CURLUE_NO_QUERY)) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - -#ifdef USE_IPV6 - /* Fill in the conn parts that do not use authority, yet. */ - conn->scope_id = conn->origin->scopeid; -#endif + /* set the connection options */ if(data->set.str[STRING_OPTIONS]) { - curlx_free(conn->options); conn->options = curlx_strdup(data->set.str[STRING_OPTIONS]); if(!conn->options) { result = CURLE_OUT_OF_MEMORY; goto out; } } + else if(data->state.up.options) { + conn->options = curlx_strdup(data->state.up.options); + if(!conn->options) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } -#ifdef CURLVERBOSE - Curl_creds_trace(data, data->state.creds, "transfer credentials"); +#ifdef USE_IPV6 + conn->scope_id = data->set.scope_id ? + data->set.scope_id : data->state.origin->scopeid; #endif out: @@ -1852,379 +1714,6 @@ static CURLcode setup_connection_internals(struct Curl_easy *data, return CURLE_OK; } -#ifndef CURL_DISABLE_PROXY - -#ifndef CURL_DISABLE_HTTP -/**************************************************************** - * Detect what (if any) proxy to use. Remember that this selects a host - * name and is not limited to HTTP proxies only. - * The returned pointer must be freed by the caller (unless NULL) - ****************************************************************/ -static char *url_detect_proxy(struct Curl_easy *data, - struct connectdata *conn) -{ - char *proxy = NULL; - - /* If proxy was not specified, we check for default proxy environment - * variables, to enable i.e Lynx compliance: - * - * http_proxy=http://some.server.dom:port/ - * https_proxy=http://some.server.dom:port/ - * ftp_proxy=http://some.server.dom:port/ - * no_proxy=domain1.dom,host.domain2.dom - * (a comma-separated list of hosts which should - * not be proxied, or an asterisk to override - * all proxy variables) - * all_proxy=http://some.server.dom:port/ - * (seems to exist for the CERN www lib. Probably - * the first to check for.) - * - * For compatibility, the all-uppercase versions of these variables are - * checked if the lowercase versions do not exist. - */ - char proxy_env[20]; - const char *envp; - VERBOSE(envp = proxy_env); - - curl_msnprintf(proxy_env, sizeof(proxy_env), "%s_proxy", - conn->scheme->name); - - /* read the protocol proxy: */ - proxy = curl_getenv(proxy_env); - - /* - * We do not try the uppercase version of HTTP_PROXY because of - * security reasons: - * - * When curl is used in a webserver application - * environment (cgi or php), this environment variable can - * be controlled by the web server user by setting the - * http header 'Proxy:' to some value. - * - * This can cause 'internal' http/ftp requests to be - * arbitrarily redirected by any external attacker. - */ - if(!proxy && !curl_strequal("http_proxy", proxy_env)) { - /* There was no lowercase variable, try the uppercase version: */ - Curl_strntoupper(proxy_env, proxy_env, sizeof(proxy_env)); - proxy = curl_getenv(proxy_env); - } - - if(!proxy) { -#ifndef CURL_DISABLE_WEBSOCKETS - /* websocket proxy fallbacks */ - 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); /* default proxy to use */ - 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 /* CURL_DISABLE_HTTP */ - -/* - * If this is supposed to use a proxy, we need to figure out the proxy - * hostname, so that we can reuse an existing connection - * that may exist registered to the same proxy host. - */ -static CURLcode parse_proxy(struct Curl_easy *data, - const char *proxy, - bool for_pre_proxy, - struct proxy_info *proxyinfo) -{ - char *proxyuser = NULL; - char *proxypasswd = NULL; - char *scheme = NULL; - CURLcode result = CURLE_OK; - /* Set the start proxy type for url scheme guessing */ - uint8_t proxytype = for_pre_proxy ? CURLPROXY_SOCKS4 : data->set.proxytype; - CURLU *uhp = curl_url(); - CURLUcode uc; - - if(!uhp) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } - /* When parsing the proxy, allowing non-supported schemes since we have - these made up ones for proxies. Guess scheme for URLs without it. */ - uc = curl_url_set(uhp, CURLUPART_URL, proxy, - CURLU_NON_SUPPORT_SCHEME | CURLU_GUESS_SCHEME); - if(!uc) { - /* parsed okay as a URL - only update proxytype when scheme was explicit */ - uc = curl_url_get(uhp, CURLUPART_SCHEME, &scheme, CURLU_NO_GUESS_SCHEME); - if(!uc) { - result = Curl_scheme_to_proxytype(data, scheme, &proxytype, proxy); - if(result) - goto error; - } - else if(uc != CURLUE_NO_SCHEME) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } - /* else: no explicit scheme, keep the configured proxytype */ - } - else { - failf(data, "Unsupported proxy syntax in \'%s\': %s", proxy, - curl_url_strerror(uc)); - result = CURLE_COULDNT_RESOLVE_PROXY; - goto error; - } - - result = Curl_peer_from_proxy_url(uhp, data, proxy, proxytype, - &proxyinfo->peer, &proxytype); - if(result) - goto error; - - switch(proxytype) { - case CURLPROXY_HTTP: - case CURLPROXY_HTTP_1_0: - case CURLPROXY_HTTPS: - case CURLPROXY_HTTPS2: - case CURLPROXY_HTTPS3: - if(for_pre_proxy) { - failf(data, "Unsupported pre-proxy type for \'%s\'", proxy); - result = CURLE_COULDNT_RESOLVE_PROXY; - goto error; - } - break; - case CURLPROXY_SOCKS4: - case CURLPROXY_SOCKS4A: - case CURLPROXY_SOCKS5: - case CURLPROXY_SOCKS5_HOSTNAME: - break; - default: - failf(data, "Unsupported proxy type %u for \'%s\'", proxytype, proxy); - result = CURLE_COULDNT_RESOLVE_PROXY; - goto error; - } - - /* Is there a username and password given in this proxy URL? */ - uc = curl_url_get(uhp, CURLUPART_USER, &proxyuser, CURLU_URLDECODE); - if(uc && (uc != CURLUE_NO_USER)) { - result = Curl_uc_to_curlcode(uc); - goto error; - } - uc = curl_url_get(uhp, CURLUPART_PASSWORD, &proxypasswd, CURLU_URLDECODE); - if(uc && (uc != CURLUE_NO_PASSWORD)) { - result = Curl_uc_to_curlcode(uc); - goto error; - } - - if(proxyuser || proxypasswd) { - result = Curl_creds_create(proxyuser, proxypasswd, NULL, NULL, - data->set.str[STRING_PROXY_SERVICE_NAME], - CREDS_URL, &proxyinfo->creds); - if(result) - goto error; - } - else if(!for_pre_proxy && - (data->set.str[STRING_PROXYUSERNAME] || - data->set.str[STRING_PROXYPASSWORD] || - data->set.str[STRING_PROXY_SERVICE_NAME])) { - /* No user/passwd in URL, if this is not a pre-proxy, the - * CURLOPT_PROXY* settings apply. */ - result = Curl_creds_create(data->set.str[STRING_PROXYUSERNAME], - data->set.str[STRING_PROXYPASSWORD], - NULL, NULL, - data->set.str[STRING_PROXY_SERVICE_NAME], - CREDS_OPTION, &proxyinfo->creds); - } - else - Curl_creds_unlink(&proxyinfo->creds); - - proxyinfo->proxytype = proxytype; - -error: - curlx_free(scheme); - curlx_free(proxyuser); - curlx_free(proxypasswd); - curl_url_cleanup(uhp); -#ifdef DEBUGBUILD - if(!result) { - DEBUGASSERT(proxyinfo); - DEBUGASSERT(proxyinfo->peer); - } -#endif - return result; -} - -static CURLcode url_set_conn_proxies(struct Curl_easy *data, - struct connectdata *conn) -{ - char *proxy = NULL; - char *pre_proxy = NULL; - char *no_proxy = NULL; - CURLcode result = CURLE_OK; - - /************************************************************* - * Detect what (if any) proxy to use - *************************************************************/ - if(data->set.str[STRING_PROXY]) { - proxy = curlx_strdup(data->set.str[STRING_PROXY]); - /* if global proxy is set, this is it */ - if(!proxy) { - failf(data, "memory shortage"); - result = CURLE_OUT_OF_MEMORY; - goto out; - } - } - - if(data->set.str[STRING_PRE_PROXY]) { - pre_proxy = curlx_strdup(data->set.str[STRING_PRE_PROXY]); - /* if global socks proxy is set, this is it */ - if(!pre_proxy) { - 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->origin->hostname, data->set.str[STRING_NOPROXY] ? - data->set.str[STRING_NOPROXY] : no_proxy)) { - curlx_safefree(proxy); - curlx_safefree(pre_proxy); - } -#ifndef CURL_DISABLE_HTTP - else if(!proxy && !pre_proxy) - /* if the host is not in the noproxy list, detect proxy. */ - proxy = url_detect_proxy(data, conn); -#endif /* CURL_DISABLE_HTTP */ - curlx_safefree(no_proxy); - - if(proxy && (!*proxy || (conn->scheme->flags & PROTOPT_NONETWORK))) { - curlx_safefree(proxy); /* Do not bother with an empty proxy string - or if the protocol does not work with network */ - } - if(pre_proxy && (!*pre_proxy || - (conn->scheme->flags & PROTOPT_NONETWORK))) { - curlx_safefree(pre_proxy); /* Do not bother with an empty socks proxy - string or if the protocol does not work - with network */ - } - - /*********************************************************************** - * If this is supposed to use a proxy, we need to figure out the proxy host - * name, proxy type and port number, so that we can reuse an existing - * connection that may exist registered to the same proxy host. - ***********************************************************************/ - if(proxy || pre_proxy) { - if(pre_proxy) { - result = parse_proxy(data, pre_proxy, TRUE, &conn->socks_proxy); - if(result) - goto out; - } - - if(proxy) { - result = parse_proxy(data, proxy, FALSE, &conn->http_proxy); - if(result) - goto out; - switch(conn->http_proxy.proxytype) { - case CURLPROXY_SOCKS4: - case CURLPROXY_SOCKS4A: - case CURLPROXY_SOCKS5: - case CURLPROXY_SOCKS5_HOSTNAME: - /* Whoops, it's not a HTTP proxy */ - if(conn->socks_proxy.peer) { - /* and we already have a SOCKS pre-proxy. Cannot have both */ - failf(data, "Having a SOCKS pre-proxy and proxy is not " - "supported with \'%s\'", proxy); - result = CURLE_COULDNT_RESOLVE_PROXY; - goto out; - } - /* switch */ - conn->socks_proxy = conn->http_proxy; - memset(&conn->http_proxy, 0, sizeof(conn->http_proxy)); - break; - default: - break; - } - } - - if(conn->http_proxy.peer) { -#ifdef CURL_DISABLE_HTTP - /* asking for an HTTP proxy is a bit funny when HTTP is disabled... */ - result = CURLE_UNSUPPORTED_PROTOCOL; - goto out; -#else -#ifndef CURL_DISABLE_DIGEST_AUTH - if(!Curl_safecmp(data->state.envproxy, proxy)) { - /* proxy changed */ - Curl_auth_digest_cleanup(&data->state.proxydigest); - curlx_free(data->state.envproxy); - data->state.envproxy = curlx_strdup(proxy); - } -#endif - if(conn->scheme->flags & PROTOPT_HTTP_PROXY_TUNNEL) { - conn->bits.tunnel_proxy = TRUE; - } - else if(!(conn->scheme->protocol & PROTO_FAMILY_HTTP)) { - /* force this connection's protocol to become HTTP if compatible */ - if((conn->scheme->flags & PROTOPT_PROXY_AS_HTTP) && - !conn->bits.tunnel_proxy) - conn->scheme = &Curl_scheme_http; - else - /* if not converting to HTTP over the proxy, enforce tunneling */ - conn->bits.tunnel_proxy = TRUE; - } -#endif - } - else - conn->bits.tunnel_proxy = FALSE; /* no tunneling if not HTTP */ - } - - conn->bits.socksproxy = !!conn->socks_proxy.peer; - conn->bits.httpproxy = !!conn->http_proxy.peer; - conn->bits.proxy = conn->bits.httpproxy || conn->bits.socksproxy; - - if(!conn->bits.proxy) { - /* we are not using the proxy after all... */ - conn->bits.proxy = FALSE; - conn->bits.httpproxy = FALSE; - conn->bits.socksproxy = FALSE; - conn->bits.tunnel_proxy = FALSE; - /* CURLPROXY_HTTPS does not have its own flag in conn->bits, yet we need - to signal that CURLPROXY_HTTPS is not used for this connection */ - conn->http_proxy.proxytype = CURLPROXY_HTTP; - } - -out: - - curlx_free(pre_proxy); - curlx_free(proxy); - return result; -} -#endif /* CURL_DISABLE_PROXY */ - /* * Curl_parse_login_details() * @@ -2601,14 +2090,6 @@ static CURLcode url_create_needle(struct Curl_easy *data, CURLcode result = CURLE_OK; bool network_scheme = TRUE; /* almost all are */ - /************************************************************* - * Check input data - *************************************************************/ - if(!Curl_bufref_ptr(&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 @@ -2627,7 +2108,7 @@ static CURLcode url_create_needle(struct Curl_easy *data, * Determine `conn->origin` and propulate `data->state.up` and * other URL related properties. *************************************************************/ - result = parseurlandfillconn(data, needle); + result = url_set_conn_origin_etc(data, needle); if(result) goto out; @@ -2661,22 +2142,11 @@ static CURLcode url_create_needle(struct Curl_easy *data, #ifndef CURL_DISABLE_PROXY /* Going via a unix socket ignores any proxy settings */ - if(needle->via_peer && needle->via_peer->unix_socket) { - needle->bits.socksproxy = FALSE; - needle->bits.httpproxy = FALSE; - needle->bits.proxy = FALSE; - } - else if(network_scheme) { - result = url_set_conn_proxies(data, needle); + if(network_scheme && + (!needle->via_peer || !needle->via_peer->unix_socket)) { + result = Curl_proxy_init_conn(data, needle); if(result) goto out; - - /************************************************************* - * If the protocol is using SSL and HTTP proxy is used, we set - * the tunnel_proxy bit. - *************************************************************/ - if((needle->given->flags & PROTOPT_SSL) && needle->bits.httpproxy) - needle->bits.tunnel_proxy = TRUE; } #endif /* CURL_DISABLE_PROXY */ @@ -2692,15 +2162,6 @@ static CURLcode url_create_needle(struct Curl_easy *data, Curl_peer_unlink(&needle->via_peer); } -#ifndef CURL_DISABLE_PROXY - /************************************************************* - * If the "connect to" feature is used with an HTTP proxy, - * we set the tunnel_proxy bit. - *************************************************************/ - if(needle->via_peer && needle->bits.httpproxy) - needle->bits.tunnel_proxy = TRUE; -#endif - /************************************************************* * Setup internals depending on protocol. Needs to be done after * we figured out what/if proxy to use. @@ -2743,6 +2204,118 @@ out: return result; } +static CURLcode url_set_data_origin_and_creds(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + CURLU *uh; + CURLUcode uc; + bool use_set_uh = (data->set.uh && !data->state.this_is_a_follow); + uint16_t port_override = data->state.allow_port ? data->set.use_port : 0; + uint32_t scope_id = 0; + + /************************************************************* + * Check input data + *************************************************************/ + if(!Curl_bufref_ptr(&data->state.url)) { + result = CURLE_URL_MALFORMAT; + goto out; + } + + up_free(data); /* cleanup previous leftovers first */ + + /* parse the URL */ + if(use_set_uh) + uh = data->state.uh = curl_url_dup(data->set.uh); + else + uh = data->state.uh = curl_url(); + if(!uh) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + /* Calculate the *real* URL this transfer uses, applying defaults + * where information is missing. */ + if(data->set.str[STRING_DEFAULT_PROTOCOL] && + !Curl_is_absolute_url(Curl_bufref_ptr(&data->state.url), NULL, 0, TRUE)) { + char *url = curl_maprintf("%s://%s", + data->set.str[STRING_DEFAULT_PROTOCOL], + Curl_bufref_ptr(&data->state.url)); + if(!url) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + Curl_bufref_set(&data->state.url, url, 0, curl_free); + } + + if(!use_set_uh) { + char *newurl; + uc = curl_url_set(uh, CURLUPART_URL, Curl_bufref_ptr(&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)); + result = Curl_uc_to_curlcode(uc); + goto out; + } + + /* after it was parsed, get the generated normalized version */ + uc = curl_url_get(uh, CURLUPART_URL, &newurl, CURLU_GET_EMPTY); + if(uc) { + result = Curl_uc_to_curlcode(uc); + goto out; + } + Curl_bufref_set(&data->state.url, newurl, 0, curl_free); + } + +#ifdef USE_IPV6 + scope_id = data->set.scope_id; +#endif + + /* `uh` is now as the connection should use it, probably. */ + result = Curl_peer_from_url(uh, data, port_override, scope_id, + &data->state.up, &data->state.origin); + if(result) + goto out; + /* The origin might get changed when HSTS applies */ + result = hsts_upgrade(data, uh, port_override, scope_id); + if(result) + goto out; + + /* When the transfers initial_origin is not set, this is the initial + * request. Remember this starting point. */ + if(!data->state.initial_origin) + Curl_peer_link(&data->state.initial_origin, data->state.origin); + + uc = curl_url_get(uh, CURLUPART_PATH, &data->state.up.path, CURLU_URLENCODE); + if(uc) { + result = Curl_uc_to_curlcode(uc); + goto out; + } + uc = curl_url_get(uh, CURLUPART_QUERY, &data->state.up.query, + CURLU_GET_EMPTY); + if(uc && (uc != CURLUE_NO_QUERY)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + uc = curl_url_get(uh, CURLUPART_OPTIONS, &data->state.up.options, + CURLU_URLDECODE); + if(uc && (uc != CURLUE_NO_OPTIONS)) { + result = Curl_uc_to_curlcode(uc); + goto out; + } + + result = url_set_data_creds(data, uh); + if(result) + goto out; + +out: + return result; +} + /** * Find an existing connection for the transfer or create a new one. * Returns @@ -2837,7 +2410,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) infof(data, "Reusing existing %s: connection%s with %s %s", conn->given->name, tls_upgraded ? " (upgraded to SSL)" : "", - conn->bits.proxy ? "proxy" : "host", + (conn->socks_proxy.peer || conn->http_proxy.peer) ? "proxy" : "host", conn->socks_proxy.peer ? conn->socks_proxy.peer->user_hostname : conn->http_proxy.peer ? conn->http_proxy.peer->user_hostname : conn->origin->hostname); @@ -2936,7 +2509,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) #ifdef CURL_DISABLE_PROXY 0 #else - data->conn->bits.proxy + (data->conn->socks_proxy.peer || data->conn->http_proxy.peer) #endif ; @@ -2953,21 +2526,33 @@ out: CURLcode Curl_connect(struct Curl_easy *data, bool *pconnected) { CURLcode result; - struct connectdata *conn; + struct connectdata *conn = NULL; *pconnected = FALSE; /* Set the request to virgin state based on transfer settings */ Curl_req_hard_reset(&data->req, data); + /* Determine the origin of the transfer and what credentials to use */ + result = url_set_data_origin_and_creds(data); + if(result) + goto out; + if(!data->state.origin) { /* just make really sure */ + DEBUGASSERT(0); + result = CURLE_FAILED_INIT; + goto out; + } /* Get or create a connection for the transfer. */ result = url_find_or_create_conn(data); conn = data->conn; - if(result) goto out; + if(!data->conn) { /* just make really sure */ + DEBUGASSERT(0); + result = CURLE_FAILED_INIT; + goto out; + } - DEBUGASSERT(conn); Curl_pgrsTime(data, TIMER_POSTQUEUE); if(conn->bits.reuse) { if(conn->attached_xfers > 1) diff --git a/lib/urldata.h b/lib/urldata.h index 97cfc5efc4..a0d7fa421d 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -62,6 +62,7 @@ #include "hostip.h" #include "hash.h" #include "peer.h" +#include "proxy.h" #include "splay.h" #include "curlx/dynbuf.h" #include "bufref.h" @@ -191,13 +192,7 @@ typedef enum { struct ConnectBits { BIT(connect_only); #ifndef CURL_DISABLE_PROXY - BIT(httpproxy); /* if set, this transfer is done through an HTTP proxy */ - BIT(socksproxy); /* if set, this transfer is done through a socks proxy */ - BIT(tunnel_proxy); /* if CONNECT is used to "tunnel" through the proxy. - This is implicit when SSL-protocols are used through - proxies, but can also be enabled explicitly by - apps */ - BIT(proxy); /* if set, this transfer is done through a proxy - any type */ + BIT(origin_is_proxy); /* if set, the connection's origin is a proxy */ #endif /* always modify bits.close with the connclose() and connkeep() macros! */ BIT(close); /* if set, we close the connection after this request */ @@ -268,12 +263,6 @@ struct ip_quadruple { ((x)->transport == TRNSPRT_UDP) || \ ((x)->transport == TRNSPRT_QUIC)) -struct proxy_info { - struct Curl_peer *peer; /* proxy to this peer */ - struct Curl_creds *creds; /* use these credentials, maybe NULL */ - uint8_t proxytype; /* what kind of proxy that is in use */ -}; - /* * The connectdata struct contains all fields and variables that should be * unique for an entire connection. @@ -437,9 +426,6 @@ struct PureInfo { session handle without disturbing information which is still alive, and that might be reused, in the connection pool. */ struct ip_quadruple primary; - int conn_remote_port; /* this is the "remote port", which is the port - number of the used URL, independent of proxy or - not */ const char *conn_scheme; uint32_t conn_protocol; struct curl_certinfo certs; /* info about the certs. Asked for with @@ -601,6 +587,9 @@ struct UrlState { Credentials from CURLOPT_* are only valid for this origin. Always set once a transfer starts searching for connections. */ struct Curl_peer *initial_origin; + /* Current origin of the transfer, changes to origin of follow + * requests. */ + struct Curl_peer *origin; int os_errno; /* filled in with errno whenever an error occurs */ int requests; /* request counter: redirects + authentication retakes */ diff --git a/lib/vauth/digest.c b/lib/vauth/digest.c index 0ed60d9039..1818fc96c7 100644 --- a/lib/vauth/digest.c +++ b/lib/vauth/digest.c @@ -427,7 +427,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, curl_msnprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]); /* Generate our SPN */ - spn = Curl_auth_build_spn(service, data->conn->origin->hostname, NULL); + spn = Curl_auth_build_spn(service, data->state.origin->hostname, NULL); if(!spn) return CURLE_OUT_OF_MEMORY; diff --git a/lib/vauth/digest_sspi.c b/lib/vauth/digest_sspi.c index 84354b4e2f..305e367e17 100644 --- a/lib/vauth/digest_sspi.c +++ b/lib/vauth/digest_sspi.c @@ -133,7 +133,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; /* Generate our SPN */ - spn = Curl_auth_build_spn(service, data->conn->origin->hostname, NULL); + spn = Curl_auth_build_spn(service, data->state.origin->hostname, NULL); if(!spn) { curlx_free(output_token); return CURLE_OUT_OF_MEMORY; diff --git a/lib/vauth/vauth.c b/lib/vauth/vauth.c index e258b44c55..3259556e5f 100644 --- a/lib/vauth/vauth.c +++ b/lib/vauth/vauth.c @@ -139,7 +139,7 @@ bool Curl_auth_user_contains_domain(struct Curl_creds *creds) */ bool Curl_auth_allowed_to_host(struct Curl_easy *data) { - return Curl_auth_allowed_to_origin(data, data->conn->origin); + return Curl_auth_allowed_to_origin(data, data->state.origin); } bool Curl_auth_allowed_to_origin(struct Curl_easy *data, diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index fe5683dbd3..581c2e4c1e 100644 --- a/lib/vquic/vquic.c +++ b/lib/vquic/vquic.c @@ -872,7 +872,7 @@ CURLcode Curl_conn_may_http3(struct Curl_easy *data, return CURLE_URL_MALFORMAT; } #ifndef CURL_DISABLE_PROXY - if(conn->bits.socksproxy) { + if(conn->socks_proxy.peer) { failf(data, "HTTP/3 is not supported over a SOCKS proxy"); return CURLE_URL_MALFORMAT; } diff --git a/tests/http/test_40_socks.py b/tests/http/test_40_socks.py index 471f1948fb..f4ae322f73 100644 --- a/tests/http/test_40_socks.py +++ b/tests/http/test_40_socks.py @@ -74,6 +74,24 @@ class TestSocks: else: r.check_response(http_status=200) + # download via socks to https: proxy (no tunnel) + @pytest.mark.parametrize("sproto", ['socks4', 'socks5']) + @pytest.mark.parametrize("proto", Env.http_h1_h2_protos()) + def test_40_02b_socks_https_proxy(self, env: Env, sproto, proto, danted: Dante, httpd): + if proto == 'h2' and not env.curl_uses_lib('nghttp2'): + pytest.skip('only supported with nghttp2') + curl = CurlClient(env=env, socks_args=[ + f'--{sproto}', f'127.0.0.1:{danted.port}' + ]) + url = f'http://localhost:{env.http_port}/data.json' + xargs = curl.get_proxy_args(proto=proto, tunnel=False) + r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True, + extra_args=xargs) + r.check_response(http_status=200) + exp_http_version = '2' if proto == 'h2' else '1.1' + assert r.stats[0]['proxy_used'] == 1, f'{r}' + assert r.stats[0]['http_version'] == exp_http_version, f'{r}' + @pytest.mark.parametrize("sproto", ['socks4', 'socks5']) @pytest.mark.parametrize("proto", Env.http_h1_h2_protos()) def test_40_03_dl_serial(self, env: Env, httpd, danted, proto, sproto): diff --git a/tests/unit/unit1614.c b/tests/unit/unit1614.c index edbaa415e1..0b6909284d 100644 --- a/tests/unit/unit1614.c +++ b/tests/unit/unit1614.c @@ -22,7 +22,7 @@ * ***************************************************************************/ #include "unitcheck.h" -#include "noproxy.h" +#include "proxy.h" static CURLcode test_unit1614(const char *arg) { @@ -176,7 +176,7 @@ static CURLcode test_unit1614(const char *arg) } #endif for(i = 0; list[i].a; i++) { - bool match = Curl_check_noproxy(list[i].a, list[i].n); + bool match = proxy_check_noproxy(list[i].a, list[i].n); if(match != list[i].match) { curl_mfprintf(stderr, "%s in %s should %smatch\n", list[i].a, list[i].n,