1
0
mirror of https://github.com/moparisthebest/curl synced 2024-12-21 15:48:49 -05:00

news: CURLOPT_CONNECT_TO and --connect-to

Makes curl connect to the given host+port instead of the host+port found
in the URL.
This commit is contained in:
Michael Kaufmann 2016-01-25 14:37:24 +01:00 committed by Daniel Stenberg
parent f86f50f05a
commit cd8d236245
25 changed files with 834 additions and 40 deletions

View File

@ -1176,6 +1176,19 @@ effectively disables the proxy. Each name in this list is matched as either
a domain which contains the hostname, or the hostname itself. For example,
local.com would match local.com, local.com:80, and www.local.com, but not
www.notlocal.com. (Added in 7.19.4).
.IP "--connect-to <host:port:connect-to-host:connect-to-port>"
For a request to the given "host:port" pair, connect to
"connect-to-host:connect-to-port" instead.
This is suitable to direct the request at a specific server, e.g. at a specific
cluster node in a cluster of servers.
This option is only used to establish the network connection. It does NOT
affect the hostname/port that is used for TLS/SSL (e.g. SNI, certificate
verification) or for the application protocols.
"host" and "port" may be the empty string, meaning "any host/port".
"connect-to-host" and "connect-to-port" may also be the empty string,
meaning "use the request's original host/port".
This option can be used many times to add many connect rules.
(Added in 7.49.0).
.IP "--ntlm"
(HTTP) Enables NTLM authentication. The NTLM authentication method was
designed by Microsoft and is used by IIS web servers. It is a proprietary

View File

@ -165,6 +165,8 @@ Proxy type. See \fICURLOPT_PROXYTYPE(3)\fP
Filter out hosts from proxy use. \fICURLOPT_NOPROXY(3)\fP
.IP CURLOPT_HTTPPROXYTUNNEL
Tunnel through the HTTP proxy. \fICURLOPT_HTTPPROXYTUNNEL(3)\fP
.IP CURLOPT_CONNECT_TO
Connect to a specific host and port. See \fICURLOPT_CONNECT_TO(3)\fP
.IP CURLOPT_SOCKS5_GSSAPI_SERVICE
Socks5 GSSAPI service name. \fICURLOPT_SOCKS5_GSSAPI_SERVICE(3)\fP
.IP CURLOPT_SOCKS5_GSSAPI_NEC

View File

@ -0,0 +1,105 @@
.\" **************************************************************************
.\" * _ _ ____ _
.\" * Project ___| | | | _ \| |
.\" * / __| | | | |_) | |
.\" * | (__| |_| | _ <| |___
.\" * \___|\___/|_| \_\_____|
.\" *
.\" * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel@haxx.se>, et al.
.\" *
.\" * This software is licensed as described in the file COPYING, which
.\" * you should have received as part of this distribution. The terms
.\" * are also available at http://curl.haxx.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.
.\" *
.\" **************************************************************************
.\"
.TH CURLOPT_CONNECT_TO 3 "10 April 2016" "libcurl 7.49.0" "curl_easy_setopt options"
.SH NAME
CURLOPT_CONNECT_TO \- Connect to a specific host and port instead of the URL's host and port
.SH SYNOPSIS
.nf
#include <curl/curl.h>
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_CONNECT_TO,
struct curl_slist *connect_to);
.fi
.SH DESCRIPTION
Pass a pointer to a linked list of strings with "connect to" information to
use for establishing network connections with this handle. The linked list
should be a fully valid list of \fBstruct curl_slist\fP structs properly
filled in. Use \fIcurl_slist_append(3)\fP to create the list and
\fIcurl_slist_free_all(3)\fP to clean up an entire list.
Each single string should be written using the format
HOST:PORT:CONNECT-TO-HOST:CONNECT-TO-PORT where HOST is the host of the
request, PORT is the port of the request, CONNECT-TO-HOST is the host name to
connect to, and CONNECT-TO-PORT is the port to connect to.
The first string that matches the request's host and port is used.
Dotted numerical IP addresses are supported for HOST and CONNECT-TO-HOST.
A numerical IPv6 address must be written within [brackets].
Any of the four values may be empty. When the HOST or PORT is empty, the host
or port will always match (the request's host or port is ignored).
When CONNECT-TO-HOST or CONNECT-TO-PORT is empty, the "connect to" feature
will be disabled for the host or port, and the request's host or port will be
used to establish the network connection.
This option is suitable to direct the request at a specific server, e.g. at a
specific cluster node in a cluster of servers.
The "connect to" host and port are only used to establish the network
connection. They do NOT affect the host and port that are used for TLS/SSL
(e.g. SNI, certificate verification) or for the application protocols.
In contrast to \fICURLOPT_RESOLVE(3)\fP, the option
\fICURLOPT_CONNECT_TO(3)\fP does not pre-populate the DNS cache and therefore
it does not affect future transfers of other easy handles that have been added
to the same multi handle.
The "connect to" host and port are ignored if they are equal to the host and
the port in the request URL, because connecting to the host and the port in
the request URL is the default behavior.
If an HTTP proxy is used for a request having a special "connect to" host or
port, and the "connect to" host or port differs from the requests's host and
port, the HTTP proxy is automatically switched to tunnel mode for this
specific request. This is necessary because it is not possible to connect to a
specific host or port in normal (non-tunnel) mode.
.SH DEFAULT
NULL
.SH PROTOCOLS
All
.SH EXAMPLE
.nf
CURL *curl;
struct curl_slist *connect_to = NULL;
host = curl_slist_append(NULL, "example.com::server1.example.com:");
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to);
curl_easy_setopt(curl, CURLOPT_URL, "http://example.com");
res = curl_easy_perform(curl);
/* always cleanup */
curl_easy_cleanup(curl);
}
curl_slist_free_all(connect_to);
.fi
.SH AVAILABILITY
Added in 7.49.0
.SH RETURN VALUE
Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not.
.SH "SEE ALSO"
.BR CURLOPT_URL "(3), " CURLOPT_RESOLVE "(3), " CURLOPT_FOLLOWLOCATION "(3), " CURLOPT_HTTPPROXYTUNNEL "(3), "

View File

@ -45,7 +45,7 @@ ADDRESS can of course be either IPv4 or IPv6 style addressing.
This option effectively pre-populates the DNS cache with entries for the
host+port pair so redirects and everything that operations against the
HOST+PORT will instead use your provided ADDRESS. Addresses set with
\fICURL_RESOLVE\fP will not time-out from the DNS cache like ordinary entries.
\fICURLOPT_RESOLVE\fP will not time-out from the DNS cache like ordinary entries.
Remove names from the DNS cache again, to stop providing these fake resolves,
by including a string in the linked list that uses the format
@ -79,4 +79,4 @@ Added in 7.21.3. Removal support added in 7.42.0.
.SH RETURN VALUE
Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not.
.SH "SEE ALSO"
.BR CURLOPT_IPRESOLVE "(3), " CURLOPT_DNS_CACHE_TIMEOUT "(3), "
.BR CURLOPT_IPRESOLVE "(3), " CURLOPT_DNS_CACHE_TIMEOUT "(3), " CURLOPT_CONNECT_TO "(3), "

View File

@ -28,7 +28,7 @@ man_MANS = CURLOPT_ACCEPT_ENCODING.3 CURLOPT_ACCEPTTIMEOUT_MS.3 \
CURLOPT_CERTINFO.3 CURLOPT_CHUNK_BGN_FUNCTION.3 CURLOPT_CHUNK_DATA.3 \
CURLOPT_CHUNK_END_FUNCTION.3 CURLOPT_CLOSESOCKETDATA.3 \
CURLOPT_CLOSESOCKETFUNCTION.3 CURLOPT_CONNECT_ONLY.3 \
CURLOPT_CONNECTTIMEOUT.3 CURLOPT_CONNECTTIMEOUT_MS.3 \
CURLOPT_CONNECTTIMEOUT.3 CURLOPT_CONNECTTIMEOUT_MS.3 CURLOPT_CONNECT_TO.3 \
CURLOPT_CONV_FROM_NETWORK_FUNCTION.3 CURLOPT_CONV_FROM_UTF8_FUNCTION.3 \
CURLOPT_CONV_TO_NETWORK_FUNCTION.3 CURLOPT_COOKIE.3 \
CURLOPT_COOKIEFILE.3 CURLOPT_COOKIEJAR.3 CURLOPT_COOKIELIST.3 \
@ -147,7 +147,7 @@ HTMLPAGES = CURLOPT_ACCEPT_ENCODING.html CURLOPT_ACCEPTTIMEOUT_MS.html \
CURLOPT_CHUNK_END_FUNCTION.html CURLOPT_CLOSESOCKETDATA.html \
CURLOPT_CLOSESOCKETFUNCTION.html CURLOPT_CONNECT_ONLY.html \
CURLOPT_CONNECTTIMEOUT.html CURLOPT_CONNECTTIMEOUT_MS.html \
CURLOPT_CONV_FROM_NETWORK_FUNCTION.html \
CURLOPT_CONNECT_TO.html CURLOPT_CONV_FROM_NETWORK_FUNCTION.html \
CURLOPT_CONV_FROM_UTF8_FUNCTION.html \
CURLOPT_CONV_TO_NETWORK_FUNCTION.html CURLOPT_COOKIE.html \
CURLOPT_COOKIEFILE.html CURLOPT_COOKIEJAR.html CURLOPT_COOKIELIST.html \
@ -281,7 +281,7 @@ PDFPAGES = CURLOPT_ACCEPT_ENCODING.pdf CURLOPT_ACCEPTTIMEOUT_MS.pdf \
CURLOPT_CLOSESOCKETDATA.pdf CURLOPT_CLOSESOCKETFUNCTION.pdf \
CURLOPT_CONNECT_ONLY.pdf CURLOPT_CONNECTTIMEOUT.pdf \
CURLOPT_CONNECTTIMEOUT_MS.pdf CURLOPT_CONV_FROM_NETWORK_FUNCTION.pdf \
CURLOPT_CONV_FROM_UTF8_FUNCTION.pdf \
CURLOPT_CONNECT_TO.pdf CURLOPT_CONV_FROM_UTF8_FUNCTION.pdf \ \
CURLOPT_CONV_TO_NETWORK_FUNCTION.pdf CURLOPT_COOKIE.pdf \
CURLOPT_COOKIEFILE.pdf CURLOPT_COOKIEJAR.pdf CURLOPT_COOKIELIST.pdf \
CURLOPT_COOKIESESSION.pdf CURLOPT_COPYPOSTFIELDS.pdf CURLOPT_CRLF.pdf \

View File

@ -331,6 +331,7 @@ CURLOPT_CLOSESOCKETFUNCTION 7.21.7
CURLOPT_CONNECTTIMEOUT 7.7
CURLOPT_CONNECTTIMEOUT_MS 7.16.2
CURLOPT_CONNECT_ONLY 7.15.2
CURLOPT_CONNECT_TO 7.49.0
CURLOPT_CONV_FROM_NETWORK_FUNCTION 7.15.4
CURLOPT_CONV_FROM_UTF8_FUNCTION 7.15.4
CURLOPT_CONV_TO_NETWORK_FUNCTION 7.15.4

View File

@ -1680,6 +1680,10 @@ typedef enum {
/* Do not send any tftp option requests to the server */
CINIT(TFTP_NO_OPTIONS, LONG, 242),
/* Linked-list of host:port:connect-to-host:connect-to-port,
overrides the URL's host:port (only for the network layer) */
CINIT(CONNECT_TO, STRINGPOINT, 243),
CURLOPT_LASTENTRY /* the last unused */
} CURLoption;

View File

@ -132,9 +132,16 @@ void Curl_conncache_destroy(struct conncache *connc)
/* returns an allocated key to find a bundle for this connection */
static char *hashkey(struct connectdata *conn)
{
return aprintf("%s:%d",
conn->bits.proxy?conn->proxy.name:conn->host.name,
conn->localport);
const char *hostname;
if(conn->bits.proxy)
hostname = conn->proxy.name;
else if(conn->bits.conn_to_host)
hostname = conn->conn_to_host.name;
else
hostname = conn->host.name;
return aprintf("%s:%d", hostname, conn->localport);
}
/* Look up the bundle with all the connections to the same host this

View File

@ -841,6 +841,8 @@ CURLcode Curl_is_connected(struct connectdata *conn,
if(result) {
/* no more addresses to try */
const char* hostname;
/* if the first address family runs out of addresses to try before
the happy eyeball timeout, go ahead and try the next family now */
if(conn->tempaddr[1] == NULL) {
@ -849,9 +851,15 @@ CURLcode Curl_is_connected(struct connectdata *conn,
return result;
}
if(conn->bits.proxy)
hostname = conn->proxy.name;
else if(conn->bits.conn_to_host)
hostname = conn->conn_to_host.name;
else
hostname = conn->host.name;
failf(data, "Failed to connect to %s port %ld: %s",
conn->bits.proxy?conn->proxy.name:conn->host.name,
conn->port, Curl_strerror(conn, error));
hostname, conn->port, Curl_strerror(conn, error));
}
return result;

View File

@ -1832,6 +1832,8 @@ CURLcode Curl_http(struct connectdata *conn, bool *done)
data->state.first_host = strdup(conn->host.name);
if(!data->state.first_host)
return CURLE_OUT_OF_MEMORY;
data->state.first_remote_port = conn->remote_port;
}
http->writebytecount = http->readbytecount = 0;

View File

@ -49,6 +49,8 @@ CURLcode Curl_proxy_connect(struct connectdata *conn)
/* for [protocol] tunneled through HTTP proxy */
struct HTTP http_proxy;
void *prot_save;
const char *hostname;
int remote_port;
CURLcode result;
/* BLOCKING */
@ -67,8 +69,16 @@ CURLcode Curl_proxy_connect(struct connectdata *conn)
memset(&http_proxy, 0, sizeof(http_proxy));
conn->data->req.protop = &http_proxy;
connkeep(conn, "HTTP proxy CONNECT");
result = Curl_proxyCONNECT(conn, FIRSTSOCKET,
conn->host.name, conn->remote_port, FALSE);
if(conn->bits.conn_to_host)
hostname = conn->conn_to_host.name;
else
hostname = conn->host.name;
if(conn->bits.conn_to_port)
remote_port = conn->conn_to_port;
else
remote_port = conn->remote_port;
result = Curl_proxyCONNECT(conn, FIRSTSOCKET, hostname,
remote_port, FALSE);
conn->data->req.protop = prot_save;
if(CURLE_OK != result)
return result;
@ -153,9 +163,14 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
const char *useragent="";
const char *http = (conn->proxytype == CURLPROXY_HTTP_1_0) ?
"1.0" : "1.1";
char *hostheader= /* host:port with IPv6 support */
aprintf("%s%s%s:%hu", conn->bits.ipv6_ip?"[":"",
hostname, conn->bits.ipv6_ip?"]":"",
bool ipv6_ip = conn->bits.ipv6_ip;
char *hostheader;
/* the hostname may be different */
if(hostname != conn->host.name)
ipv6_ip = (strchr(hostname, ':') != NULL);
hostheader= /* host:port with IPv6 support */
aprintf("%s%s%s:%hu", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
remote_port);
if(!hostheader) {
Curl_add_buffer_free(req_buffer);

View File

@ -1464,9 +1464,15 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
{
struct Curl_dns_entry *dns = NULL;
struct connectdata *conn = data->easy_conn;
const char *hostname;
if(conn->bits.conn_to_host)
hostname = conn->conn_to_host.name;
else
hostname = conn->host.name;
/* check if we have the name resolved by now */
dns = Curl_fetch_addr(conn, conn->host.name, (int)conn->port);
dns = Curl_fetch_addr(conn, hostname, (int)conn->port);
if(dns) {
#ifdef CURLRES_ASYNCH

331
lib/url.c
View File

@ -2674,6 +2674,9 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
break;
#endif
}
case CURLOPT_CONNECT_TO:
data->set.connect_to = va_arg(param, struct curl_slist *);
break;
default:
/* unknown tag and its companion, just ignore: */
result = CURLE_UNKNOWN_OPTION;
@ -2729,6 +2732,7 @@ static void conn_free(struct connectdata *conn)
Curl_safefree(conn->allocptr.rtsp_transport);
Curl_safefree(conn->trailer);
Curl_safefree(conn->host.rawalloc); /* host name buffer */
Curl_safefree(conn->conn_to_host.rawalloc); /* host name buffer */
Curl_safefree(conn->proxy.rawalloc); /* proxy name buffer */
Curl_safefree(conn->master_buffer);
@ -2787,6 +2791,7 @@ CURLcode Curl_disconnect(struct connectdata *conn, bool dead_connection)
Curl_conncache_remove_conn(data->state.conn_cache, conn);
free_fixed_hostname(&conn->host);
free_fixed_hostname(&conn->conn_to_host);
free_fixed_hostname(&conn->proxy);
Curl_ssl_close(conn, FIRSTSOCKET);
@ -3146,9 +3151,15 @@ ConnectionExists(struct SessionHandle *data,
max_pipeline_length(data->multi):0;
size_t best_pipe_len = max_pipe_len;
struct curl_llist_element *curr;
const char *hostname;
if(needle->bits.conn_to_host)
hostname = needle->conn_to_host.name;
else
hostname = needle->host.name;
infof(data, "Found bundle for host %s: %p [%s]\n",
needle->host.name, (void *)bundle,
hostname, (void *)bundle,
(bundle->multiuse== BUNDLE_PIPELINING?
"can pipeline":
(bundle->multiuse== BUNDLE_MULTIPLEX?
@ -3267,6 +3278,16 @@ ConnectionExists(struct SessionHandle *data,
/* don't do mixed proxy and non-proxy connections */
continue;
if(needle->bits.conn_to_host != check->bits.conn_to_host)
/* don't mix connections that use the "connect to host" feature and
* connections that don't use this feature */
continue;
if(needle->bits.conn_to_port != check->bits.conn_to_port)
/* don't mix connections that use the "connect to port" feature and
* connections that don't use this feature */
continue;
if(!canPipeline && check->inuse)
/* this request can't be pipelined but the checked connection is
already in use so we skip it */
@ -3302,7 +3323,7 @@ ConnectionExists(struct SessionHandle *data,
}
}
if(!needle->bits.httpproxy || needle->handler->flags&PROTOPT_SSL ||
if(!needle->bits.httpproxy || (needle->handler->flags&PROTOPT_SSL) ||
(needle->bits.httpproxy && check->bits.httpproxy &&
needle->bits.tunnel_proxy && check->bits.tunnel_proxy &&
Curl_raw_equal(needle->proxy.name, check->proxy.name) &&
@ -3313,6 +3334,10 @@ ConnectionExists(struct SessionHandle *data,
if((Curl_raw_equal(needle->handler->scheme, check->handler->scheme) ||
(get_protocol_family(check->handler->protocol) ==
needle->handler->protocol && check->tls_upgraded)) &&
(!needle->bits.conn_to_host || Curl_raw_equal(
needle->conn_to_host.name, check->conn_to_host.name)) &&
(!needle->bits.conn_to_port ||
needle->conn_to_port == check->conn_to_port) &&
Curl_raw_equal(needle->host.name, check->host.name) &&
needle->remote_port == check->remote_port) {
/* The schemes match or the the protocol family is the same and the
@ -3490,16 +3515,27 @@ CURLcode Curl_connected_proxy(struct connectdata *conn,
case CURLPROXY_SOCKS5:
case CURLPROXY_SOCKS5_HOSTNAME:
return Curl_SOCKS5(conn->proxyuser, conn->proxypasswd,
conn->host.name, conn->remote_port,
conn->bits.conn_to_host ? conn->conn_to_host.name :
conn->host.name,
conn->bits.conn_to_port ? conn->conn_to_port :
conn->remote_port,
FIRSTSOCKET, conn);
case CURLPROXY_SOCKS4:
return Curl_SOCKS4(conn->proxyuser, conn->host.name,
conn->remote_port, FIRSTSOCKET, conn, FALSE);
return Curl_SOCKS4(conn->proxyuser,
conn->bits.conn_to_host ? conn->conn_to_host.name :
conn->host.name,
conn->bits.conn_to_port ? conn->conn_to_port :
conn->remote_port,
FIRSTSOCKET, conn, FALSE);
case CURLPROXY_SOCKS4A:
return Curl_SOCKS4(conn->proxyuser, conn->host.name,
conn->remote_port, FIRSTSOCKET, conn, TRUE);
return Curl_SOCKS4(conn->proxyuser,
conn->bits.conn_to_host ? conn->conn_to_host.name :
conn->host.name,
conn->bits.conn_to_port ? conn->conn_to_port :
conn->remote_port,
FIRSTSOCKET, conn, TRUE);
#endif /* CURL_DISABLE_PROXY */
case CURLPROXY_HTTP:
@ -4373,11 +4409,6 @@ static CURLcode setup_connection_internals(struct connectdata *conn)
was very likely already set to the proxy port */
conn->port = p->defport;
/* only if remote_port was not already parsed off the URL we use the
default port number */
if(conn->remote_port < 0)
conn->remote_port = (unsigned short)conn->given->defport;
return CURLE_OK;
}
@ -4652,7 +4683,7 @@ static CURLcode parse_proxy(struct SessionHandle *data,
if(strncmp("%25", ptr, 3))
infof(data, "Please URL encode %% as %%25, see RFC 6874.\n");
ptr++;
/* Allow unresered characters as defined in RFC 3986 */
/* Allow unreserved characters as defined in RFC 3986 */
while(*ptr && (ISALPHA(*ptr) || ISXDIGIT(*ptr) || (*ptr == '-') ||
(*ptr == '.') || (*ptr == '_') || (*ptr == '~')))
ptr++;
@ -4679,7 +4710,7 @@ static CURLcode parse_proxy(struct SessionHandle *data,
/* now set the local port number */
port = strtol(prox_portno, &endp, 10);
if((endp && *endp && (*endp != '/') && (*endp != ' ')) ||
(port >= 65536) ) {
(port < 0) || (port > 65535)) {
/* meant to detect for example invalid IPv6 numerical addresses without
brackets: "2a00:fac0:a000::7:13". Accept a trailing slash only
because we then allow "URL style" with the number followed by a
@ -4702,7 +4733,7 @@ static CURLcode parse_proxy(struct SessionHandle *data,
a slash so we strip everything from the first slash */
atsign = strchr(proxyptr, '/');
if(atsign)
*atsign = 0x0; /* cut off path part from host name */
*atsign = '\0'; /* cut off path part from host name */
if(data->set.proxyport)
/* None given in the proxy string, then get the default one if it is
@ -5106,6 +5137,12 @@ static CURLcode parse_remote_port(struct SessionHandle *data,
use the default port. Firefox and Chrome both do that. */
*portptr = '\0';
}
/* only if remote_port was not already parsed off the URL we use the
default port number */
if(conn->remote_port < 0)
conn->remote_port = (unsigned short)conn->given->defport;
return CURLE_OK;
}
@ -5211,6 +5248,214 @@ static CURLcode set_login(struct connectdata *conn,
return result;
}
/*
* Parses a "host:port" string to connect to.
* The hostname and the port may be empty; in this case, NULL is returned for
* the hostname and -1 for the port.
*/
static CURLcode parse_connect_to_host_port(struct SessionHandle *data,
const char *host,
char **hostname_result,
int *port_result)
{
char *host_dup;
char *hostptr;
char *host_portno;
char *portptr;
int port = -1;
*hostname_result = NULL;
*port_result = -1;
if(!host || !*host)
return CURLE_OK;
host_dup = strdup(host);
if(!host_dup)
return CURLE_OUT_OF_MEMORY;
hostptr = host_dup;
/* start scanning for port number at this point */
portptr = hostptr;
/* detect and extract RFC6874-style IPv6-addresses */
if(*hostptr == '[') {
char *ptr = ++hostptr; /* advance beyond the initial bracket */
while(*ptr && (ISXDIGIT(*ptr) || (*ptr == ':') || (*ptr == '.')))
ptr++;
if(*ptr == '%') {
/* There might be a zone identifier */
if(strncmp("%25", ptr, 3))
infof(data, "Please URL encode %% as %%25, see RFC 6874.\n");
ptr++;
/* Allow unreserved characters as defined in RFC 3986 */
while(*ptr && (ISALPHA(*ptr) || ISXDIGIT(*ptr) || (*ptr == '-') ||
(*ptr == '.') || (*ptr == '_') || (*ptr == '~')))
ptr++;
}
if(*ptr == ']')
/* yeps, it ended nicely with a bracket as well */
*ptr++ = '\0';
else
infof(data, "Invalid IPv6 address format\n");
portptr = ptr;
/* Note that if this didn't end with a bracket, we still advanced the
* hostptr first, but I can't see anything wrong with that as no host
* name nor a numeric can legally start with a bracket.
*/
}
/* Get port number off server.com:1080 */
host_portno = strchr(portptr, ':');
if(host_portno) {
char *endp = NULL;
*host_portno = '\0'; /* cut off number from host name */
host_portno++;
if(*host_portno) {
long portparse = strtol(host_portno, &endp, 10);
if((endp && *endp) || (portparse < 0) || (portparse > 65535)) {
infof(data, "No valid port number in connect to host string (%s)\n",
host_portno);
hostptr = NULL;
port = -1;
}
else
port = (int)portparse; /* we know it will fit */
}
}
/* now, clone the cleaned host name */
if(hostptr) {
*hostname_result = strdup(hostptr);
if(!*hostname_result) {
free(host_dup);
return CURLE_OUT_OF_MEMORY;
}
}
*port_result = port;
free(host_dup);
return CURLE_OK;
}
/*
* Parses one "connect to" string in the form:
* "HOST:PORT:CONNECT-TO-HOST:CONNECT-TO-PORT".
*/
static CURLcode parse_connect_to_string(struct SessionHandle *data,
struct connectdata *conn,
const char *conn_to_host,
char **host_result,
int *port_result)
{
CURLcode result = CURLE_OK;
const char *ptr = conn_to_host;
int host_match = FALSE;
int port_match = FALSE;
if(*ptr == ':') {
/* an empty hostname always matches */
host_match = TRUE;
ptr++;
}
else {
/* check whether the URL's hostname matches */
size_t hostname_to_match_len;
char *hostname_to_match = aprintf("%s%s%s",
conn->bits.ipv6_ip ? "[" : "",
conn->host.name,
conn->bits.ipv6_ip ? "]" : "");
if(!hostname_to_match)
return CURLE_OUT_OF_MEMORY;
hostname_to_match_len = strlen(hostname_to_match);
host_match = curl_strnequal(ptr, hostname_to_match, hostname_to_match_len);
free(hostname_to_match);
ptr += hostname_to_match_len;
host_match = host_match && *ptr == ':';
ptr++;
}
if(host_match) {
if(*ptr == ':') {
/* an empty port always matches */
port_match = TRUE;
ptr++;
}
else {
/* check whether the URL's port matches */
char *ptr_next = strchr(ptr, ':');
if(ptr_next) {
char *endp = NULL;
long port_to_match = strtol(ptr, &endp, 10);
if((endp == ptr_next) && (port_to_match == conn->remote_port)) {
port_match = TRUE;
ptr = ptr_next + 1;
}
}
}
}
if(host_match && port_match) {
/* parse the hostname and port to connect to */
result = parse_connect_to_host_port(data, ptr, host_result, port_result);
}
return result;
}
/*
* Processes all strings in the "connect to" slist, and uses the "connect
* to host" and "connect to port" of the first string that matches.
*/
static CURLcode parse_connect_to_slist(struct SessionHandle *data,
struct connectdata *conn,
struct curl_slist *conn_to_host)
{
CURLcode result = CURLE_OK;
char *host = NULL;
int port = 0;
while(conn_to_host && !host) {
result = parse_connect_to_string(data, conn, conn_to_host->data,
&host, &port);
if(result)
return result;
if(host && *host) {
bool ipv6host;
conn->conn_to_host.rawalloc = host;
conn->conn_to_host.name = host;
conn->bits.conn_to_host = TRUE;
ipv6host = strchr(host, ':') != NULL;
infof(data, "Connecting to hostname: %s%s%s\n",
ipv6host ? "[" : "", host, ipv6host ? "]" : "");
}
else {
/* no "connect to host" */
conn->bits.conn_to_host = FALSE;
free(host);
}
if(port >= 0) {
conn->conn_to_port = port;
conn->bits.conn_to_port = TRUE;
infof(data, "Connecting to port: %d\n", port);
}
else {
/* no "connect to port" */
conn->bits.conn_to_port = FALSE;
}
conn_to_host = conn_to_host->next;
}
return result;
}
/*************************************************************
* Resolve the address of the server or proxy
*************************************************************/
@ -5262,12 +5507,21 @@ static CURLcode resolve_server(struct SessionHandle *data,
else
#endif
if(!conn->proxy.name || !*conn->proxy.name) {
struct hostname *connhost;
if(conn->bits.conn_to_host)
connhost = &conn->conn_to_host;
else
connhost = &conn->host;
/* If not connecting via a proxy, extract the port from the URL, if it is
* there, thus overriding any defaults that might have been set above. */
conn->port = conn->remote_port; /* it is the same port */
if(conn->bits.conn_to_port)
conn->port = conn->conn_to_port;
else
conn->port = conn->remote_port; /* it is the same port */
/* Resolve target host right on */
rc = Curl_resolv_timeout(conn, conn->host.name, (int)conn->port,
rc = Curl_resolv_timeout(conn, connhost->name, (int)conn->port,
&hostaddr, timeout_ms);
if(rc == CURLRESOLV_PENDING)
*async = TRUE;
@ -5276,7 +5530,7 @@ static CURLcode resolve_server(struct SessionHandle *data,
result = CURLE_OPERATION_TIMEDOUT;
else if(!hostaddr) {
failf(data, "Couldn't resolve host '%s'", conn->host.dispname);
failf(data, "Couldn't resolve host '%s'", connhost->dispname);
result = CURLE_COULDNT_RESOLVE_HOST;
/* don't return yet, we need to clean up the timeout first */
}
@ -5351,8 +5605,14 @@ static void reuse_conn(struct connectdata *old_conn,
/* host can change, when doing keepalive with a proxy or if the case is
different this time etc */
free_fixed_hostname(&conn->host);
free_fixed_hostname(&conn->conn_to_host);
Curl_safefree(conn->host.rawalloc);
Curl_safefree(conn->conn_to_host.rawalloc);
conn->host=old_conn->host;
conn->bits.conn_to_host = old_conn->bits.conn_to_host;
conn->conn_to_host = old_conn->conn_to_host;
conn->bits.conn_to_port = old_conn->bits.conn_to_port;
conn->conn_to_port = old_conn->conn_to_port;
/* persist connection info in session handle */
Curl_persistconninfo(conn);
@ -5660,13 +5920,48 @@ static CURLcode create_conn(struct SessionHandle *data,
if(result)
goto out;
/*************************************************************
* Process the "connect to" linked list of hostname/port mappings.
* Do this after the remote port number has been fixed in the URL.
*************************************************************/
result = parse_connect_to_slist(data, conn, data->set.connect_to);
if(result)
goto out;
/*************************************************************
* IDN-fix the hostnames
*************************************************************/
fix_hostname(data, conn, &conn->host);
if(conn->bits.conn_to_host)
fix_hostname(data, conn, &conn->conn_to_host);
if(conn->proxy.name && *conn->proxy.name)
fix_hostname(data, conn, &conn->proxy);
/*************************************************************
* Check whether the host and the "connect to host" are equal.
* Do this after the hostnames have been IDN-fixed .
*************************************************************/
if(conn->bits.conn_to_host &&
Curl_raw_equal(conn->conn_to_host.name, conn->host.name)) {
conn->bits.conn_to_host = FALSE;
}
/*************************************************************
* Check whether the port and the "connect to port" are equal.
* Do this after the remote port number has been fixed in the URL.
*************************************************************/
if(conn->bits.conn_to_port && conn->conn_to_port == conn->remote_port) {
conn->bits.conn_to_port = FALSE;
}
/*************************************************************
* If the "connect to" feature is used with an HTTP proxy,
* we set the tunnel_proxy bit.
*************************************************************/
if((conn->bits.conn_to_host || conn->bits.conn_to_port) &&
conn->bits.httpproxy)
conn->bits.tunnel_proxy = TRUE;
/*************************************************************
* Setup internals depending on protocol. Needs to be done after
* we figured out what/if proxy to use.

View File

@ -375,10 +375,12 @@ struct ssl_config_data {
/* information stored about one single SSL session */
struct curl_ssl_session {
char *name; /* host name for which this ID was used */
char *conn_to_host; /* host name for the connection (may be NULL) */
void *sessionid; /* as returned from the SSL layer */
size_t idsize; /* if known, otherwise 0 */
long age; /* just a number, the higher the more recent */
int remote_port; /* remote port to connect to */
int remote_port; /* remote port */
int conn_to_port; /* remote port for the connection (may be -1) */
struct ssl_config_data ssl_config; /* setup for this session */
};
@ -490,6 +492,10 @@ struct ConnectBits {
/* always modify bits.close with the connclose() and connkeep() macros! */
bool close; /* if set, we close the connection after this request */
bool reuse; /* if set, this is a re-used connection */
bool conn_to_host; /* if set, this connection has a "connect to host"
that overrides the host in the URL */
bool conn_to_port; /* if set, this connection has a "connect to port"
that overrides the port in the URL (remote port) */
bool proxy; /* if set, this transfer is done through a proxy - any type */
bool httpproxy; /* if set, this transfer is done through a http proxy */
bool user_passwd; /* do we use user+password for this connection? */
@ -874,10 +880,14 @@ struct connectdata {
int socktype; /* SOCK_STREAM or SOCK_DGRAM */
struct hostname host;
struct hostname conn_to_host; /* the host to connect to. valid only if
bits.conn_to_host is set */
struct hostname proxy;
long port; /* which port to use locally */
int remote_port; /* what remote port to connect to, not the proxy port! */
int remote_port; /* the remote port, not the proxy port! */
int conn_to_port; /* the remote port to connect to. valid only if
bits.conn_to_port is set */
/* 'primary_ip' and 'primary_port' get filled with peer's numerical
ip address and port number whenever an outgoing connection is
@ -1226,11 +1236,13 @@ struct UrlState {
bytes / second */
bool this_is_a_follow; /* this is a followed Location: request */
char *first_host; /* if set, this should be the host name that we will
char *first_host; /* host name of the first (not followed) request.
if set, this should be the host name that we will
sent authorization to, no else. Used to make Location:
following not keep sending user+password... This is
strdup() data.
*/
int first_remote_port; /* remote port of the first (not followed) request */
struct curl_ssl_session *session; /* array of 'max_ssl_sessions' size */
long sessionage; /* number of the most recent session */
char *tempwrite; /* allocated buffer to keep data in when a write
@ -1528,6 +1540,8 @@ struct UserDefined {
struct curl_slist *telnet_options; /* linked list of telnet options */
struct curl_slist *resolve; /* list of names to add/remove from
DNS cache */
struct curl_slist *connect_to; /* list of host:port mappings to override
the hostname and port to connect to */
curl_TimeCond timecondition; /* kind of time/date comparison */
time_t timevalue; /* what time to compare with */
Curl_HttpReq httpreq; /* what kind of HTTP request (if any) is this */
@ -1575,7 +1589,6 @@ struct UserDefined {
bool http_set_referer; /* is a custom referer used */
bool http_auto_referer; /* set "correct" referer when following location: */
bool opt_no_body; /* as set with CURLOPT_NOBODY */
bool set_port; /* custom port number used */
bool upload; /* upload request */
enum CURL_NETRC_OPTION
use_netrc; /* defined in include/curl.h */

View File

@ -363,6 +363,12 @@ bool Curl_ssl_getsessionid(struct connectdata *conn,
/* not session ID means blank entry */
continue;
if(Curl_raw_equal(conn->host.name, check->name) &&
((!conn->bits.conn_to_host && !check->conn_to_host) ||
(conn->bits.conn_to_host && check->conn_to_host &&
Curl_raw_equal(conn->conn_to_host.name, check->conn_to_host))) &&
((!conn->bits.conn_to_port && check->conn_to_port == -1) ||
(conn->bits.conn_to_port && check->conn_to_port != -1 &&
conn->conn_to_port == check->conn_to_port)) &&
(conn->remote_port == check->remote_port) &&
Curl_ssl_config_matches(&conn->ssl_config, &check->ssl_config)) {
/* yes, we have a session ID! */
@ -400,6 +406,7 @@ void Curl_ssl_kill_session(struct curl_ssl_session *session)
Curl_free_ssl_config(&session->ssl_config);
Curl_safefree(session->name);
Curl_safefree(session->conn_to_host);
}
}
@ -442,6 +449,8 @@ CURLcode Curl_ssl_addsessionid(struct connectdata *conn,
struct curl_ssl_session *store = &data->state.session[0];
long oldest_age=data->state.session[0].age; /* zero if unused */
char *clone_host;
char *clone_conn_to_host;
int conn_to_port;
long *general_age;
/* Even though session ID re-use might be disabled, that only disables USING
@ -452,6 +461,21 @@ CURLcode Curl_ssl_addsessionid(struct connectdata *conn,
if(!clone_host)
return CURLE_OUT_OF_MEMORY; /* bail out */
if(conn->bits.conn_to_host) {
clone_conn_to_host = strdup(conn->conn_to_host.name);
if(!clone_conn_to_host) {
free(clone_host);
return CURLE_OUT_OF_MEMORY; /* bail out */
}
}
else
clone_conn_to_host = NULL;
if(conn->bits.conn_to_port)
conn_to_port = conn->conn_to_port;
else
conn_to_port = -1;
/* Now we should add the session ID and the host name to the cache, (remove
the oldest if necessary) */
@ -484,10 +508,12 @@ CURLcode Curl_ssl_addsessionid(struct connectdata *conn,
store->age = *general_age; /* set current age */
/* free it if there's one already present */
free(store->name);
free(store->conn_to_host);
store->name = clone_host; /* clone host name */
store->conn_to_host = clone_conn_to_host; /* clone connect to host name */
store->conn_to_port = conn_to_port; /* connect to port number */
store->remote_port = conn->remote_port; /* port number */
/* Unlock */
if(SSLSESSION_SHARED(data))
Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION);
@ -495,6 +521,7 @@ CURLcode Curl_ssl_addsessionid(struct connectdata *conn,
if(!Curl_clone_ssl_config(&conn->ssl_config, &store->ssl_config)) {
store->sessionid = NULL; /* let caller free sessionid */
free(clone_host);
free(clone_conn_to_host);
return CURLE_OUT_OF_MEMORY;
}

View File

@ -135,6 +135,7 @@ static void free_config_fields(struct OperationConfig *config)
curl_slist_free_all(config->telnet_options);
curl_slist_free_all(config->resolve);
curl_slist_free_all(config->connect_to);
Curl_safefree(config->socksproxy);
Curl_safefree(config->proxy_service_name);

View File

@ -151,6 +151,7 @@ struct OperationConfig {
struct curl_httppost *last_post;
struct curl_slist *telnet_options;
struct curl_slist *resolve;
struct curl_slist *connect_to;
HttpReq httpreq;
/* for bandwidth limiting features: */

View File

@ -183,6 +183,7 @@ static const struct LongShort aliases[]= {
{"$Q", "proto-default", TRUE},
{"$R", "expect100-timeout", TRUE},
{"$S", "tftp-no-options", FALSE},
{"$U", "connect-to", TRUE},
{"0", "http1.0", FALSE},
{"01", "http1.1", FALSE},
{"02", "http2", FALSE},
@ -1009,6 +1010,11 @@ ParameterError getparameter(char *flag, /* f or -long-flag */
case 'S': /* --tftp-no-options */
config->tftp_no_options = toggle;
break;
case 'U': /* --connect-to */
err = add2list(&config->connect_to, nextarg);
if(err)
return err;
break;
}
break;
case '#': /* --progress-bar */

View File

@ -58,6 +58,7 @@ static const char *const helptext[] = {
" --compressed Request compressed response (using deflate or gzip)",
" -K, --config FILE Read config from FILE",
" --connect-timeout SECONDS Maximum time allowed for connection",
" --connect-to HOST1:PORT1:HOST2:PORT2 Connect to host (network level)",
" -C, --continue-at OFFSET Resumed transfer OFFSET",
" -b, --cookie STRING/FILE Read cookies from STRING/FILE (H)",
" -c, --cookie-jar FILE Write cookies to FILE after operation (H)",

View File

@ -1305,6 +1305,10 @@ static CURLcode operate_do(struct GlobalConfig *global,
/* new in 7.21.3 */
my_setopt_slist(curl, CURLOPT_RESOLVE, config->resolve);
if(config->connect_to)
/* new in 7.49.0 */
my_setopt_slist(curl, CURLOPT_CONNECT_TO, config->connect_to);
/* new in 7.21.4 */
if(curlinfo->features & CURL_VERSION_TLSAUTH_SRP) {
if(config->tls_username)

View File

@ -169,4 +169,4 @@ test2016 test2017 test2018 test2019 test2020 test2021 test2022 test2023 \
test2024 test2025 test2026 test2027 test2028 test2029 test2030 test2031 \
test2032 test2033 test2034 test2035 test2036 test2037 test2038 test2039 \
test2040 test2041 test2042 test2043 test2044 test2045 test2046 test2047 \
test2048
test2048 test2049 test2050 test2051 test2052

64
tests/data/test2049 Normal file
View File

@ -0,0 +1,64 @@
<testcase>
<info>
<keywords>
HTTP
HTTP GET
CURLOPT_CONNECT_TO
</keywords>
</info>
#
# Server-side
<reply>
<data>
HTTP/1.1 200 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Content-Length: 3
Content-Type: text/plain
OK
</data>
</reply>
#
# Client-side
<client>
<server>
http
</server>
<name>
Connect to specific host
</name>
<command>
http://www1.example.com:8081/2049 --connect-to ::%HOSTIP:%HTTPPORT --next http://www2.example.com:8082/2049 --connect-to :8082:%HOSTIP:%HTTPPORT --next http://www3.example.com:8083/2049 --connect-to www3.example.com::%HOSTIP:%HTTPPORT --next http://www4.example.com:8084/2049 --connect-to www4.example.com:8084:%HOSTIP:%HTTPPORT
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<strip>
^User-Agent:.*
</strip>
<protocol>
GET /2049 HTTP/1.1
Host: www1.example.com:8081
Accept: */*
GET /2049 HTTP/1.1
Host: www2.example.com:8082
Accept: */*
GET /2049 HTTP/1.1
Host: www3.example.com:8083
Accept: */*
GET /2049 HTTP/1.1
Host: www4.example.com:8084
Accept: */*
</protocol>
</verify>
</testcase>

77
tests/data/test2050 Normal file
View File

@ -0,0 +1,77 @@
<testcase>
<info>
<keywords>
HTTP
HTTP GET
HTTP CONNECT
HTTP proxy
proxytunnel
CURLOPT_CONNECT_TO
</keywords>
</info>
#
# Server-side
<reply>
<connect>
HTTP/1.1 200 Connection established
</connect>
<data>
HTTP/1.1 200 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Content-Length: 3
Content-Type: text/plain
OK
</data>
<datacheck>
HTTP/1.1 200 Connection established
HTTP/1.1 200 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Content-Length: 3
Content-Type: text/plain
OK
</datacheck>
</reply>
#
# Client-side
<client>
<server>
http
http-proxy
</server>
<name>
Connect to specific host via HTTP proxy (switch to tunnel mode automatically)
</name>
<command>
http://www.example.com.2050/2050 --connect-to ::connect.example.com.2050:%HTTPPORT -x %HOSTIP:%PROXYPORT
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<strip>
^User-Agent:.*
</strip>
<proxy>
CONNECT connect.example.com.2050:%HTTPPORT HTTP/1.1
Host: connect.example.com.2050:%HTTPPORT
</proxy>
<protocol>
GET /2050 HTTP/1.1
Host: www.example.com.2050
Accept: */*
</protocol>
</verify>
</testcase>

74
tests/data/test2051 Normal file
View File

@ -0,0 +1,74 @@
<testcase>
<info>
<keywords>
HTTP
HTTP GET
CURLOPT_CONNECT_TO
</keywords>
</info>
#
# Server-side
<reply>
<data nocheck="yes">
HTTP/1.1 200 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Content-Length: 3
Content-Type: text/plain
OK
</data>
</reply>
#
# Client-side
<client>
<server>
http
</server>
<name>
Connect to specific host: Re-use existing connections if possible
</name>
<command>
http://%HOSTIP:%HTTPPORT/2051 -w "%{num_connects}\n" --next --connect-to ::%HOSTIP:%HTTPPORT http://%HOSTIP:%HTTPPORT/2051 -w "%{num_connects}\n" --next http://%HOSTIP:%HTTPPORT/2051 -w "%{num_connects}\n"
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<strip>
^User-Agent:.*
</strip>
<protocol>
GET /2051 HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Accept: */*
GET /2051 HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Accept: */*
GET /2051 HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Accept: */*
</protocol>
<stdout>
HTTP/1.1 200 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Content-Length: 3
Content-Type: text/plain
OK
1
OK
0
OK
0
</stdout>
</verify>
</testcase>

68
tests/data/test2052 Normal file
View File

@ -0,0 +1,68 @@
<testcase>
<info>
<keywords>
HTTP
HTTP GET
CURLOPT_CONNECT_TO
</keywords>
</info>
#
# Server-side
<reply>
<data nocheck="yes">
HTTP/1.1 200 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Content-Length: 3
Content-Type: text/plain
OK
</data>
</reply>
#
# Client-side
<client>
<server>
http
</server>
<name>
Connect to specific host: Do not mix connections with and without a "connect to host"
</name>
<command>
http://www.example.com:%HTTPPORT/2052 --resolve www.example.com:%HTTPPORT:%HOSTIP -w "%{num_connects}\n" --next --resolve -www.example.com:%HTTPPORT --connect-to ::%HOSTIP:%HTTPPORT http://www.example.com:%HTTPPORT/2052 -w "%{num_connects}\n"
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<strip>
^User-Agent:.*
</strip>
<protocol>
GET /2052 HTTP/1.1
Host: www.example.com:%HTTPPORT
Accept: */*
GET /2052 HTTP/1.1
Host: www.example.com:%HTTPPORT
Accept: */*
</protocol>
<stdout>
HTTP/1.1 200 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Content-Length: 3
Content-Type: text/plain
OK
1
OK
1
</stdout>
</verify>
</testcase>