From cd8d23624594e21c37a0453459229a90a38ad471 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Mon, 25 Jan 2016 14:37:24 +0100 Subject: [PATCH] news: CURLOPT_CONNECT_TO and --connect-to Makes curl connect to the given host+port instead of the host+port found in the URL. --- docs/curl.1 | 13 + docs/libcurl/curl_easy_setopt.3 | 2 + docs/libcurl/opts/CURLOPT_CONNECT_TO.3 | 105 ++++++++ docs/libcurl/opts/CURLOPT_RESOLVE.3 | 4 +- docs/libcurl/opts/Makefile.am | 6 +- docs/libcurl/symbols-in-versions | 1 + include/curl/curl.h | 4 + lib/conncache.c | 13 +- lib/connect.c | 12 +- lib/http.c | 2 + lib/http_proxy.c | 25 +- lib/multi.c | 8 +- lib/url.c | 331 +++++++++++++++++++++++-- lib/urldata.h | 21 +- lib/vtls/vtls.c | 29 ++- src/tool_cfgable.c | 1 + src/tool_cfgable.h | 1 + src/tool_getparam.c | 6 + src/tool_help.c | 1 + src/tool_operate.c | 4 + tests/data/Makefile.inc | 2 +- tests/data/test2049 | 64 +++++ tests/data/test2050 | 77 ++++++ tests/data/test2051 | 74 ++++++ tests/data/test2052 | 68 +++++ 25 files changed, 834 insertions(+), 40 deletions(-) create mode 100644 docs/libcurl/opts/CURLOPT_CONNECT_TO.3 create mode 100644 tests/data/test2049 create mode 100644 tests/data/test2050 create mode 100644 tests/data/test2051 create mode 100644 tests/data/test2052 diff --git a/docs/curl.1 b/docs/curl.1 index 523e05df3..e7d92001a 100644 --- a/docs/curl.1 +++ b/docs/curl.1 @@ -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 " +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 diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3 index 6905a96cd..07f704b09 100644 --- a/docs/libcurl/curl_easy_setopt.3 +++ b/docs/libcurl/curl_easy_setopt.3 @@ -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 diff --git a/docs/libcurl/opts/CURLOPT_CONNECT_TO.3 b/docs/libcurl/opts/CURLOPT_CONNECT_TO.3 new file mode 100644 index 000000000..d80336e24 --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_CONNECT_TO.3 @@ -0,0 +1,105 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2016, 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 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 + +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), " diff --git a/docs/libcurl/opts/CURLOPT_RESOLVE.3 b/docs/libcurl/opts/CURLOPT_RESOLVE.3 index a4da9b5a7..3eb1e9577 100644 --- a/docs/libcurl/opts/CURLOPT_RESOLVE.3 +++ b/docs/libcurl/opts/CURLOPT_RESOLVE.3 @@ -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), " diff --git a/docs/libcurl/opts/Makefile.am b/docs/libcurl/opts/Makefile.am index 00afc0057..c8c6657c0 100644 --- a/docs/libcurl/opts/Makefile.am +++ b/docs/libcurl/opts/Makefile.am @@ -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 \ diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index 524d4ff62..ba144ec45 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -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 diff --git a/include/curl/curl.h b/include/curl/curl.h index 6d9710117..1df508410 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -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; diff --git a/lib/conncache.c b/lib/conncache.c index 6e03caf3c..89086590a 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -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 diff --git a/lib/connect.c b/lib/connect.c index 567186a69..3f3d3f65f 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -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; diff --git a/lib/http.c b/lib/http.c index 91ab8326d..1b1cd2235 100644 --- a/lib/http.c +++ b/lib/http.c @@ -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; diff --git a/lib/http_proxy.c b/lib/http_proxy.c index e082ba291..676f0cb5d 100644 --- a/lib/http_proxy.c +++ b/lib/http_proxy.c @@ -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); diff --git a/lib/multi.c b/lib/multi.c index b1c1f5396..aec377d96 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -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 diff --git a/lib/url.c b/lib/url.c index b597becb2..c93491582 100644 --- a/lib/url.c +++ b/lib/url.c @@ -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. diff --git a/lib/urldata.h b/lib/urldata.h index 0efb65160..aea688ced 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -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 */ diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index 36465a7f6..69fb70fc7 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -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; } diff --git a/src/tool_cfgable.c b/src/tool_cfgable.c index 4517714cd..8b60a91b7 100644 --- a/src/tool_cfgable.c +++ b/src/tool_cfgable.c @@ -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); diff --git a/src/tool_cfgable.h b/src/tool_cfgable.h index ba91062c6..e6f4e840d 100644 --- a/src/tool_cfgable.h +++ b/src/tool_cfgable.h @@ -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: */ diff --git a/src/tool_getparam.c b/src/tool_getparam.c index 70290d19e..3dea7a775 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -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 */ diff --git a/src/tool_help.c b/src/tool_help.c index 42159f4d9..8b4551ba2 100644 --- a/src/tool_help.c +++ b/src/tool_help.c @@ -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)", diff --git a/src/tool_operate.c b/src/tool_operate.c index c8bf12bb2..deabf90b3 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -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) diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index a13aac9b9..bc0ee4ce3 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -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 diff --git a/tests/data/test2049 b/tests/data/test2049 new file mode 100644 index 000000000..efa576e98 --- /dev/null +++ b/tests/data/test2049 @@ -0,0 +1,64 @@ + + + +HTTP +HTTP GET +CURLOPT_CONNECT_TO + + + +# +# Server-side + + +HTTP/1.1 200 OK +Date: Thu, 09 Nov 2010 14:49:00 GMT +Content-Length: 3 +Content-Type: text/plain + +OK + + + +# +# Client-side + + +http + + +Connect to specific host + + + +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 + + + +# +# Verify data after the test has been "shot" + + +^User-Agent:.* + + +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: */* + + + + + diff --git a/tests/data/test2050 b/tests/data/test2050 new file mode 100644 index 000000000..805e872e4 --- /dev/null +++ b/tests/data/test2050 @@ -0,0 +1,77 @@ + + + +HTTP +HTTP GET +HTTP CONNECT +HTTP proxy +proxytunnel +CURLOPT_CONNECT_TO + + + +# +# Server-side + + +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 + + + +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 + + + +# +# Client-side + + +http +http-proxy + + +Connect to specific host via HTTP proxy (switch to tunnel mode automatically) + + + +http://www.example.com.2050/2050 --connect-to ::connect.example.com.2050:%HTTPPORT -x %HOSTIP:%PROXYPORT + + + +# +# Verify data after the test has been "shot" + + +^User-Agent:.* + + +CONNECT connect.example.com.2050:%HTTPPORT HTTP/1.1 +Host: connect.example.com.2050:%HTTPPORT + + + +GET /2050 HTTP/1.1 +Host: www.example.com.2050 +Accept: */* + + + + + diff --git a/tests/data/test2051 b/tests/data/test2051 new file mode 100644 index 000000000..e8c6bed1d --- /dev/null +++ b/tests/data/test2051 @@ -0,0 +1,74 @@ + + + +HTTP +HTTP GET +CURLOPT_CONNECT_TO + + + +# +# Server-side + + +HTTP/1.1 200 OK +Date: Thu, 09 Nov 2010 14:49:00 GMT +Content-Length: 3 +Content-Type: text/plain + +OK + + + +# +# Client-side + + +http + + +Connect to specific host: Re-use existing connections if possible + + + +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" + + + +# +# Verify data after the test has been "shot" + + +^User-Agent:.* + + +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: */* + + + + +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 + + + + diff --git a/tests/data/test2052 b/tests/data/test2052 new file mode 100644 index 000000000..082002f2c --- /dev/null +++ b/tests/data/test2052 @@ -0,0 +1,68 @@ + + + +HTTP +HTTP GET +CURLOPT_CONNECT_TO + + + +# +# Server-side + + +HTTP/1.1 200 OK +Date: Thu, 09 Nov 2010 14:49:00 GMT +Content-Length: 3 +Content-Type: text/plain + +OK + + + +# +# Client-side + + +http + + +Connect to specific host: Do not mix connections with and without a "connect to host" + + + +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" + + + +# +# Verify data after the test has been "shot" + + +^User-Agent:.* + + +GET /2052 HTTP/1.1 +Host: www.example.com:%HTTPPORT +Accept: */* + +GET /2052 HTTP/1.1 +Host: www.example.com:%HTTPPORT +Accept: */* + + + + +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 + + + +