]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
news: CURLOPT_CONNECT_TO and --connect-to
authorMichael Kaufmann <mail@michael-kaufmann.ch>
Mon, 25 Jan 2016 13:37:24 +0000 (14:37 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Sun, 17 Apr 2016 21:50:59 +0000 (23:50 +0200)
Makes curl connect to the given host+port instead of the host+port found
in the URL.

25 files changed:
docs/curl.1
docs/libcurl/curl_easy_setopt.3
docs/libcurl/opts/CURLOPT_CONNECT_TO.3 [new file with mode: 0644]
docs/libcurl/opts/CURLOPT_RESOLVE.3
docs/libcurl/opts/Makefile.am
docs/libcurl/symbols-in-versions
include/curl/curl.h
lib/conncache.c
lib/connect.c
lib/http.c
lib/http_proxy.c
lib/multi.c
lib/url.c
lib/urldata.h
lib/vtls/vtls.c
src/tool_cfgable.c
src/tool_cfgable.h
src/tool_getparam.c
src/tool_help.c
src/tool_operate.c
tests/data/Makefile.inc
tests/data/test2049 [new file with mode: 0644]
tests/data/test2050 [new file with mode: 0644]
tests/data/test2051 [new file with mode: 0644]
tests/data/test2052 [new file with mode: 0644]

index 523e05df3ac5d54889f120bc411bec32e3c30538..e7d92001a0de3956cc488c3e2e5cafa73e8482ad 100644 (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
index 6905a96cd0966d6df4eb807b3353ce91374c87f6..07f704b094ccb5c03fe8b5a5367728bdb20d3829 100644 (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
diff --git a/docs/libcurl/opts/CURLOPT_CONNECT_TO.3 b/docs/libcurl/opts/CURLOPT_CONNECT_TO.3
new file mode 100644 (file)
index 0000000..d80336e
--- /dev/null
@@ -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), "
index a4da9b5a705c55f9bdf969196d1be95c7a024481..3eb1e95779ccd01b5f514316d3b64451b31a86a6 100644 (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), "
index 00afc0057b46ce136c48a08164de44cf3de8a2e2..c8c6657c05343ec720093f1aaadb9ed0e5f696f5 100644 (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 \
index 524d4ff62b17e49c215b5ba43a6f868061417839..ba144ec4531f6a2a6d3b1689232c89e76c85c1d1 100644 (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
index 6d9710117e5c3ea54845faee5dd0e9b59ab8cf61..1df508410733a834ccf9714bf32869efa95c7629 100644 (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;
 
index 6e03caf3c9881f3d6f918f823ba8e1bba06632e2..89086590aabb2dd191e04e67e6062d2e9a8ce047 100644 (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
index 567186a6943d454f02338117d6d0478a07f7518d..3f3d3f65f2c117e77b3f386db75dc0146ddc9c96 100644 (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;
index 91ab8326d22bfa6c3419d3ce62ae9f16206f14e2..1b1cd2235eae4a329a81c4137a0dea358ffd43b1 100644 (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;
 
index e082ba291a81f47748165f38efbf612a16d52266..676f0cb5de75e4845dc82b053a4dc26767fe9628 100644 (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);
index b1c1f53961f722327dd59a855619531caf633961..aec377d96c2f28596b3215160ed2d8243410dbfc 100644 (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
index b597becb24aa18637bb4060fe670f04d43e79791..c93491582c85750b93bdc5297b84dad648fecc89 100644 (file)
--- 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.
index 0efb65160334bf71caeacb80eea6d6659b141d0b..aea688ced546a5a9e62bf9473744756e4cf4adb4 100644 (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 */
index 36465a7f60e023dbeefd1f1272708a3e0922037d..69fb70fc772a2b84a4003a23a249a69772382b6c 100644 (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;
   }
 
index 4517714cda05d456807b156e7ffb93b3df1f51c7..8b60a91b7041f22c21628566671885fec4cf9adf 100644 (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);
index ba91062c6ff53e313f194c06745a1c5941c1eb8d..e6f4e840d9cec612181193e6fd560605c3c06fb0 100644 (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: */
index 70290d19e5704f255e2474d38d2b6bdfd07ab561..3dea7a775695febec9d89d6eceb2c6571f52fe40 100644 (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 */
index 42159f4d919548812e93b170e627b0772eb7ba50..8b4551ba2895390dec43974c9b510f7d5a96b293 100644 (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)",
index c8bf12bb266048c37f2a628f750d5f71623bcfbe..deabf90b3c88a78c23f0d0bbedc661c1e626d5e6 100644 (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)
index a13aac9b90d560bca4058254e376ec716c52447d..bc0ee4ce31b2570cc6365996265d5694ba0c8cfd 100644 (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
diff --git a/tests/data/test2049 b/tests/data/test2049
new file mode 100644 (file)
index 0000000..efa576e
--- /dev/null
@@ -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\r
+Host: www1.example.com:8081\r
+Accept: */*\r
+\r
+GET /2049 HTTP/1.1\r
+Host: www2.example.com:8082\r
+Accept: */*\r
+\r
+GET /2049 HTTP/1.1\r
+Host: www3.example.com:8083\r
+Accept: */*\r
+\r
+GET /2049 HTTP/1.1\r
+Host: www4.example.com:8084\r
+Accept: */*\r
+\r
+</protocol>
+
+</verify>
+</testcase>
diff --git a/tests/data/test2050 b/tests/data/test2050
new file mode 100644 (file)
index 0000000..805e872
--- /dev/null
@@ -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\r
+Host: connect.example.com.2050:%HTTPPORT\r
+\r
+</proxy>
+<protocol>
+GET /2050 HTTP/1.1\r
+Host: www.example.com.2050\r
+Accept: */*\r
+\r
+</protocol>
+
+</verify>
+</testcase>
diff --git a/tests/data/test2051 b/tests/data/test2051
new file mode 100644 (file)
index 0000000..e8c6bed
--- /dev/null
@@ -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\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+GET /2051 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+GET /2051 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+</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>
diff --git a/tests/data/test2052 b/tests/data/test2052
new file mode 100644 (file)
index 0000000..082002f
--- /dev/null
@@ -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\r
+Host: www.example.com:%HTTPPORT\r
+Accept: */*\r
+\r
+GET /2052 HTTP/1.1\r
+Host: www.example.com:%HTTPPORT\r
+Accept: */*\r
+\r
+</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>