• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.haxx.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  ***************************************************************************/
22 
23 #include "curl_setup.h"
24 
25 #include "http_proxy.h"
26 
27 #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
28 
29 #include <curl/curl.h>
30 #include "sendf.h"
31 #include "http.h"
32 #include "url.h"
33 #include "select.h"
34 #include "progress.h"
35 #include "non-ascii.h"
36 #include "connect.h"
37 #include "curlx.h"
38 #include "vtls/vtls.h"
39 
40 /* The last 3 #include files should be in this order */
41 #include "curl_printf.h"
42 #include "curl_memory.h"
43 #include "memdebug.h"
44 
45 /*
46  * Perform SSL initialization for HTTPS proxy.  Sets
47  * proxy_ssl_connected connection bit when complete.  Can be
48  * called multiple times.
49  */
https_proxy_connect(struct connectdata * conn,int sockindex)50 static CURLcode https_proxy_connect(struct connectdata *conn, int sockindex)
51 {
52 #ifdef USE_SSL
53   CURLcode result = CURLE_OK;
54   DEBUGASSERT(conn->http_proxy.proxytype == CURLPROXY_HTTPS);
55   if(!conn->bits.proxy_ssl_connected[sockindex]) {
56     /* perform SSL initialization for this socket */
57     result =
58       Curl_ssl_connect_nonblocking(conn, sockindex,
59                                    &conn->bits.proxy_ssl_connected[sockindex]);
60     if(result)
61       conn->bits.close = TRUE; /* a failed connection is marked for closure to
62                                   prevent (bad) re-use or similar */
63   }
64   return result;
65 #else
66   (void) conn;
67   (void) sockindex;
68   return CURLE_NOT_BUILT_IN;
69 #endif
70 }
71 
Curl_proxy_connect(struct connectdata * conn,int sockindex)72 CURLcode Curl_proxy_connect(struct connectdata *conn, int sockindex)
73 {
74   if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) {
75     const CURLcode result = https_proxy_connect(conn, sockindex);
76     if(result)
77       return result;
78     if(!conn->bits.proxy_ssl_connected[sockindex])
79       return result; /* wait for HTTPS proxy SSL initialization to complete */
80   }
81 
82   if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
83 #ifndef CURL_DISABLE_PROXY
84     /* for [protocol] tunneled through HTTP proxy */
85     struct HTTP http_proxy;
86     void *prot_save;
87     const char *hostname;
88     int remote_port;
89     CURLcode result;
90 
91     /* BLOCKING */
92     /* We want "seamless" operations through HTTP proxy tunnel */
93 
94     /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the
95      * member conn->proto.http; we want [protocol] through HTTP and we have
96      * to change the member temporarily for connecting to the HTTP
97      * proxy. After Curl_proxyCONNECT we have to set back the member to the
98      * original pointer
99      *
100      * This function might be called several times in the multi interface case
101      * if the proxy's CONNECT response is not instant.
102      */
103     prot_save = conn->data->req.protop;
104     memset(&http_proxy, 0, sizeof(http_proxy));
105     conn->data->req.protop = &http_proxy;
106     connkeep(conn, "HTTP proxy CONNECT");
107 
108     /* for the secondary socket (FTP), use the "connect to host"
109      * but ignore the "connect to port" (use the secondary port)
110      */
111 
112     if(conn->bits.conn_to_host)
113       hostname = conn->conn_to_host.name;
114     else if(sockindex == SECONDARYSOCKET)
115       hostname = conn->secondaryhostname;
116     else
117       hostname = conn->host.name;
118 
119     if(sockindex == SECONDARYSOCKET)
120       remote_port = conn->secondary_port;
121     else if(conn->bits.conn_to_port)
122       remote_port = conn->conn_to_port;
123     else
124       remote_port = conn->remote_port;
125     result = Curl_proxyCONNECT(conn, sockindex, hostname, remote_port);
126     conn->data->req.protop = prot_save;
127     if(CURLE_OK != result)
128       return result;
129     Curl_safefree(conn->allocptr.proxyuserpwd);
130 #else
131     return CURLE_NOT_BUILT_IN;
132 #endif
133   }
134   /* no HTTP tunnel proxy, just return */
135   return CURLE_OK;
136 }
137 
Curl_connect_complete(struct connectdata * conn)138 bool Curl_connect_complete(struct connectdata *conn)
139 {
140   return !conn->connect_state ||
141     (conn->connect_state->tunnel_state == TUNNEL_COMPLETE);
142 }
143 
Curl_connect_ongoing(struct connectdata * conn)144 bool Curl_connect_ongoing(struct connectdata *conn)
145 {
146   return conn->connect_state &&
147     (conn->connect_state->tunnel_state != TUNNEL_COMPLETE);
148 }
149 
connect_init(struct connectdata * conn,bool reinit)150 static CURLcode connect_init(struct connectdata *conn, bool reinit)
151 {
152   struct http_connect_state *s;
153   if(!reinit) {
154     DEBUGASSERT(!conn->connect_state);
155     s = calloc(1, sizeof(struct http_connect_state));
156     if(!s)
157       return CURLE_OUT_OF_MEMORY;
158     infof(conn->data, "allocate connect buffer!\n");
159     conn->connect_state = s;
160   }
161   else {
162     DEBUGASSERT(conn->connect_state);
163     s = conn->connect_state;
164   }
165   s->tunnel_state = TUNNEL_INIT;
166   s->keepon = TRUE;
167   s->line_start = s->connect_buffer;
168   s->ptr = s->line_start;
169   s->cl = 0;
170   s->close_connection = FALSE;
171   return CURLE_OK;
172 }
173 
connect_done(struct connectdata * conn)174 static void connect_done(struct connectdata *conn)
175 {
176   struct http_connect_state *s = conn->connect_state;
177   s->tunnel_state = TUNNEL_COMPLETE;
178   infof(conn->data, "CONNECT phase completed!\n");
179 }
180 
CONNECT(struct connectdata * conn,int sockindex,const char * hostname,int remote_port)181 static CURLcode CONNECT(struct connectdata *conn,
182                         int sockindex,
183                         const char *hostname,
184                         int remote_port)
185 {
186   int subversion = 0;
187   struct Curl_easy *data = conn->data;
188   struct SingleRequest *k = &data->req;
189   CURLcode result;
190   curl_socket_t tunnelsocket = conn->sock[sockindex];
191   struct http_connect_state *s = conn->connect_state;
192 
193 #define SELECT_OK      0
194 #define SELECT_ERROR   1
195 
196   if(Curl_connect_complete(conn))
197     return CURLE_OK; /* CONNECT is already completed */
198 
199   conn->bits.proxy_connect_closed = FALSE;
200 
201   do {
202     timediff_t check;
203     if(TUNNEL_INIT == s->tunnel_state) {
204       /* BEGIN CONNECT PHASE */
205       char *host_port;
206       Curl_send_buffer *req_buffer;
207 
208       infof(data, "Establish HTTP proxy tunnel to %s:%d\n",
209             hostname, remote_port);
210 
211         /* This only happens if we've looped here due to authentication
212            reasons, and we don't really use the newly cloned URL here
213            then. Just free() it. */
214       free(data->req.newurl);
215       data->req.newurl = NULL;
216 
217       /* initialize a dynamic send-buffer */
218       req_buffer = Curl_add_buffer_init();
219 
220       if(!req_buffer)
221         return CURLE_OUT_OF_MEMORY;
222 
223       host_port = aprintf("%s:%d", hostname, remote_port);
224       if(!host_port) {
225         Curl_add_buffer_free(&req_buffer);
226         return CURLE_OUT_OF_MEMORY;
227       }
228 
229       /* Setup the proxy-authorization header, if any */
230       result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
231 
232       free(host_port);
233 
234       if(!result) {
235         char *host = NULL;
236         const char *proxyconn = "";
237         const char *useragent = "";
238         const char *http = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ?
239           "1.0" : "1.1";
240         bool ipv6_ip = conn->bits.ipv6_ip;
241         char *hostheader;
242 
243         /* the hostname may be different */
244         if(hostname != conn->host.name)
245           ipv6_ip = (strchr(hostname, ':') != NULL);
246         hostheader = /* host:port with IPv6 support */
247           aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
248                   remote_port);
249         if(!hostheader) {
250           Curl_add_buffer_free(&req_buffer);
251           return CURLE_OUT_OF_MEMORY;
252         }
253 
254         if(!Curl_checkProxyheaders(conn, "Host")) {
255           host = aprintf("Host: %s\r\n", hostheader);
256           if(!host) {
257             free(hostheader);
258             Curl_add_buffer_free(&req_buffer);
259             return CURLE_OUT_OF_MEMORY;
260           }
261         }
262         if(!Curl_checkProxyheaders(conn, "Proxy-Connection"))
263           proxyconn = "Proxy-Connection: Keep-Alive\r\n";
264 
265         if(!Curl_checkProxyheaders(conn, "User-Agent") &&
266            data->set.str[STRING_USERAGENT])
267           useragent = conn->allocptr.uagent;
268 
269         result =
270           Curl_add_bufferf(&req_buffer,
271                            "CONNECT %s HTTP/%s\r\n"
272                            "%s"  /* Host: */
273                            "%s"  /* Proxy-Authorization */
274                            "%s"  /* User-Agent */
275                            "%s", /* Proxy-Connection */
276                            hostheader,
277                            http,
278                            host?host:"",
279                            conn->allocptr.proxyuserpwd?
280                            conn->allocptr.proxyuserpwd:"",
281                            useragent,
282                            proxyconn);
283 
284         if(host)
285           free(host);
286         free(hostheader);
287 
288         if(!result)
289           result = Curl_add_custom_headers(conn, TRUE, req_buffer);
290 
291         if(!result)
292           /* CRLF terminate the request */
293           result = Curl_add_bufferf(&req_buffer, "\r\n");
294 
295         if(!result) {
296           /* Send the connect request to the proxy */
297           /* BLOCKING */
298           result =
299             Curl_add_buffer_send(&req_buffer, conn,
300                                  &data->info.request_size, 0, sockindex);
301         }
302         req_buffer = NULL;
303         if(result)
304           failf(data, "Failed sending CONNECT to proxy");
305       }
306 
307       Curl_add_buffer_free(&req_buffer);
308       if(result)
309         return result;
310 
311       s->tunnel_state = TUNNEL_CONNECT;
312       s->perline = 0;
313     } /* END CONNECT PHASE */
314 
315     check = Curl_timeleft(data, NULL, TRUE);
316     if(check <= 0) {
317       failf(data, "Proxy CONNECT aborted due to timeout");
318       return CURLE_OPERATION_TIMEDOUT;
319     }
320 
321     if(!Curl_conn_data_pending(conn, sockindex))
322       /* return so we'll be called again polling-style */
323       return CURLE_OK;
324 
325     /* at this point, the tunnel_connecting phase is over. */
326 
327     { /* READING RESPONSE PHASE */
328       int error = SELECT_OK;
329 
330       while(s->keepon) {
331         ssize_t gotbytes;
332 
333         /* make sure we have space to read more data */
334         if(s->ptr >= &s->connect_buffer[CONNECT_BUFFER_SIZE]) {
335           failf(data, "CONNECT response too large!");
336           return CURLE_RECV_ERROR;
337         }
338 
339         /* Read one byte at a time to avoid a race condition. Wait at most one
340            second before looping to ensure continuous pgrsUpdates. */
341         result = Curl_read(conn, tunnelsocket, s->ptr, 1, &gotbytes);
342         if(result == CURLE_AGAIN)
343           /* socket buffer drained, return */
344           return CURLE_OK;
345 
346         if(Curl_pgrsUpdate(conn))
347           return CURLE_ABORTED_BY_CALLBACK;
348 
349         if(result) {
350           s->keepon = FALSE;
351           break;
352         }
353         else if(gotbytes <= 0) {
354           if(data->set.proxyauth && data->state.authproxy.avail) {
355             /* proxy auth was requested and there was proxy auth available,
356                then deem this as "mere" proxy disconnect */
357             conn->bits.proxy_connect_closed = TRUE;
358             infof(data, "Proxy CONNECT connection closed\n");
359           }
360           else {
361             error = SELECT_ERROR;
362             failf(data, "Proxy CONNECT aborted");
363           }
364           s->keepon = FALSE;
365           break;
366         }
367 
368 
369         if(s->keepon > TRUE) {
370           /* This means we are currently ignoring a response-body */
371 
372           s->ptr = s->connect_buffer;
373           if(s->cl) {
374             /* A Content-Length based body: simply count down the counter
375                and make sure to break out of the loop when we're done! */
376             s->cl--;
377             if(s->cl <= 0) {
378               s->keepon = FALSE;
379               s->tunnel_state = TUNNEL_COMPLETE;
380               break;
381             }
382           }
383           else {
384             /* chunked-encoded body, so we need to do the chunked dance
385                properly to know when the end of the body is reached */
386             CHUNKcode r;
387             CURLcode extra;
388             ssize_t tookcareof = 0;
389 
390             /* now parse the chunked piece of data so that we can
391                properly tell when the stream ends */
392             r = Curl_httpchunk_read(conn, s->ptr, 1, &tookcareof, &extra);
393             if(r == CHUNKE_STOP) {
394               /* we're done reading chunks! */
395               infof(data, "chunk reading DONE\n");
396               s->keepon = FALSE;
397               /* we did the full CONNECT treatment, go COMPLETE */
398               s->tunnel_state = TUNNEL_COMPLETE;
399             }
400           }
401           continue;
402         }
403 
404         s->perline++; /* amount of bytes in this line so far */
405 
406         /* if this is not the end of a header line then continue */
407         if(*s->ptr != 0x0a) {
408           s->ptr++;
409           continue;
410         }
411 
412         /* convert from the network encoding */
413         result = Curl_convert_from_network(data, s->line_start,
414                                            (size_t)s->perline);
415         /* Curl_convert_from_network calls failf if unsuccessful */
416         if(result)
417           return result;
418 
419         /* output debug if that is requested */
420         if(data->set.verbose)
421           Curl_debug(data, CURLINFO_HEADER_IN,
422                      s->line_start, (size_t)s->perline);
423 
424         if(!data->set.suppress_connect_headers) {
425           /* send the header to the callback */
426           int writetype = CLIENTWRITE_HEADER;
427           if(data->set.include_header)
428             writetype |= CLIENTWRITE_BODY;
429 
430           result = Curl_client_write(conn, writetype,
431                                      s->line_start, s->perline);
432           if(result)
433             return result;
434         }
435 
436         data->info.header_size += (long)s->perline;
437         data->req.headerbytecount += (long)s->perline;
438 
439         /* Newlines are CRLF, so the CR is ignored as the line isn't
440            really terminated until the LF comes. Treat a following CR
441            as end-of-headers as well.*/
442 
443         if(('\r' == s->line_start[0]) ||
444            ('\n' == s->line_start[0])) {
445           /* end of response-headers from the proxy */
446           s->ptr = s->connect_buffer;
447           if((407 == k->httpcode) && !data->state.authproblem) {
448             /* If we get a 407 response code with content length
449                when we have no auth problem, we must ignore the
450                whole response-body */
451             s->keepon = 2;
452 
453             if(s->cl) {
454               infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
455                     " bytes of response-body\n", s->cl);
456             }
457             else if(s->chunked_encoding) {
458               CHUNKcode r;
459               CURLcode extra;
460 
461               infof(data, "Ignore chunked response-body\n");
462 
463               /* We set ignorebody true here since the chunked
464                  decoder function will acknowledge that. Pay
465                  attention so that this is cleared again when this
466                  function returns! */
467               k->ignorebody = TRUE;
468 
469               if(s->line_start[1] == '\n') {
470                 /* this can only be a LF if the letter at index 0
471                    was a CR */
472                 s->line_start++;
473               }
474 
475               /* now parse the chunked piece of data so that we can
476                  properly tell when the stream ends */
477               r = Curl_httpchunk_read(conn, s->line_start + 1, 1, &gotbytes,
478                                       &extra);
479               if(r == CHUNKE_STOP) {
480                 /* we're done reading chunks! */
481                 infof(data, "chunk reading DONE\n");
482                 s->keepon = FALSE;
483                 /* we did the full CONNECT treatment, go to COMPLETE */
484                 s->tunnel_state = TUNNEL_COMPLETE;
485               }
486             }
487             else {
488               /* without content-length or chunked encoding, we
489                  can't keep the connection alive since the close is
490                  the end signal so we bail out at once instead */
491               s->keepon = FALSE;
492             }
493           }
494           else
495             s->keepon = FALSE;
496           if(!s->cl)
497             /* we did the full CONNECT treatment, go to COMPLETE */
498             s->tunnel_state = TUNNEL_COMPLETE;
499           continue;
500         }
501 
502         s->line_start[s->perline] = 0; /* zero terminate the buffer */
503         if((checkprefix("WWW-Authenticate:", s->line_start) &&
504             (401 == k->httpcode)) ||
505            (checkprefix("Proxy-authenticate:", s->line_start) &&
506             (407 == k->httpcode))) {
507 
508           bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
509           char *auth = Curl_copy_header_value(s->line_start);
510           if(!auth)
511             return CURLE_OUT_OF_MEMORY;
512 
513           result = Curl_http_input_auth(conn, proxy, auth);
514 
515           free(auth);
516 
517           if(result)
518             return result;
519         }
520         else if(checkprefix("Content-Length:", s->line_start)) {
521           if(k->httpcode/100 == 2) {
522             /* A client MUST ignore any Content-Length or Transfer-Encoding
523                header fields received in a successful response to CONNECT.
524                "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
525             infof(data, "Ignoring Content-Length in CONNECT %03d response\n",
526                   k->httpcode);
527           }
528           else {
529             (void)curlx_strtoofft(s->line_start +
530                                   strlen("Content-Length:"), NULL, 10, &s->cl);
531           }
532         }
533         else if(Curl_compareheader(s->line_start, "Connection:", "close"))
534           s->close_connection = TRUE;
535         else if(checkprefix("Transfer-Encoding:", s->line_start)) {
536           if(k->httpcode/100 == 2) {
537             /* A client MUST ignore any Content-Length or Transfer-Encoding
538                header fields received in a successful response to CONNECT.
539                "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
540             infof(data, "Ignoring Transfer-Encoding in "
541                   "CONNECT %03d response\n", k->httpcode);
542           }
543           else if(Curl_compareheader(s->line_start,
544                                      "Transfer-Encoding:", "chunked")) {
545             infof(data, "CONNECT responded chunked\n");
546             s->chunked_encoding = TRUE;
547             /* init our chunky engine */
548             Curl_httpchunk_init(conn);
549           }
550         }
551         else if(Curl_compareheader(s->line_start,
552                                    "Proxy-Connection:", "close"))
553           s->close_connection = TRUE;
554         else if(2 == sscanf(s->line_start, "HTTP/1.%d %d",
555                             &subversion,
556                             &k->httpcode)) {
557           /* store the HTTP code from the proxy */
558           data->info.httpproxycode = k->httpcode;
559         }
560 
561         s->perline = 0; /* line starts over here */
562         s->ptr = s->connect_buffer;
563         s->line_start = s->ptr;
564       } /* while there's buffer left and loop is requested */
565 
566       if(Curl_pgrsUpdate(conn))
567         return CURLE_ABORTED_BY_CALLBACK;
568 
569       if(error)
570         return CURLE_RECV_ERROR;
571 
572       if(data->info.httpproxycode/100 != 2) {
573         /* Deal with the possibly already received authenticate
574            headers. 'newurl' is set to a new URL if we must loop. */
575         result = Curl_http_auth_act(conn);
576         if(result)
577           return result;
578 
579         if(conn->bits.close)
580           /* the connection has been marked for closure, most likely in the
581              Curl_http_auth_act() function and thus we can kill it at once
582              below */
583           s->close_connection = TRUE;
584       }
585 
586       if(s->close_connection && data->req.newurl) {
587         /* Connection closed by server. Don't use it anymore */
588         Curl_closesocket(conn, conn->sock[sockindex]);
589         conn->sock[sockindex] = CURL_SOCKET_BAD;
590         break;
591       }
592     } /* END READING RESPONSE PHASE */
593 
594     /* If we are supposed to continue and request a new URL, which basically
595      * means the HTTP authentication is still going on so if the tunnel
596      * is complete we start over in INIT state */
597     if(data->req.newurl && (TUNNEL_COMPLETE == s->tunnel_state)) {
598       connect_init(conn, TRUE); /* reinit */
599     }
600 
601   } while(data->req.newurl);
602 
603   if(data->info.httpproxycode/100 != 2) {
604     if(s->close_connection && data->req.newurl) {
605       conn->bits.proxy_connect_closed = TRUE;
606       infof(data, "Connect me again please\n");
607       connect_done(conn);
608     }
609     else {
610       free(data->req.newurl);
611       data->req.newurl = NULL;
612       /* failure, close this connection to avoid re-use */
613       streamclose(conn, "proxy CONNECT failure");
614       Curl_closesocket(conn, conn->sock[sockindex]);
615       conn->sock[sockindex] = CURL_SOCKET_BAD;
616     }
617 
618     /* to back to init state */
619     s->tunnel_state = TUNNEL_INIT;
620 
621     if(conn->bits.proxy_connect_closed)
622       /* this is not an error, just part of the connection negotiation */
623       return CURLE_OK;
624     failf(data, "Received HTTP code %d from proxy after CONNECT",
625           data->req.httpcode);
626     return CURLE_RECV_ERROR;
627   }
628 
629   s->tunnel_state = TUNNEL_COMPLETE;
630 
631   /* If a proxy-authorization header was used for the proxy, then we should
632      make sure that it isn't accidentally used for the document request
633      after we've connected. So let's free and clear it here. */
634   Curl_safefree(conn->allocptr.proxyuserpwd);
635   conn->allocptr.proxyuserpwd = NULL;
636 
637   data->state.authproxy.done = TRUE;
638   data->state.authproxy.multipass = FALSE;
639 
640   infof(data, "Proxy replied %d to CONNECT request\n",
641         data->info.httpproxycode);
642   data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
643   conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the
644                                          document request  */
645   return CURLE_OK;
646 }
647 
Curl_connect_free(struct Curl_easy * data)648 void Curl_connect_free(struct Curl_easy *data)
649 {
650   struct connectdata *conn = data->conn;
651   struct http_connect_state *s = conn->connect_state;
652   if(s) {
653     free(s);
654     conn->connect_state = NULL;
655   }
656 }
657 
658 /*
659  * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
660  * function will issue the necessary commands to get a seamless tunnel through
661  * this proxy. After that, the socket can be used just as a normal socket.
662  */
663 
Curl_proxyCONNECT(struct connectdata * conn,int sockindex,const char * hostname,int remote_port)664 CURLcode Curl_proxyCONNECT(struct connectdata *conn,
665                            int sockindex,
666                            const char *hostname,
667                            int remote_port)
668 {
669   CURLcode result;
670   if(!conn->connect_state) {
671     result = connect_init(conn, FALSE);
672     if(result)
673       return result;
674   }
675   result = CONNECT(conn, sockindex, hostname, remote_port);
676 
677   if(result || Curl_connect_complete(conn))
678     connect_done(conn);
679 
680   return result;
681 }
682 
683 #else
Curl_connect_free(struct Curl_easy * data)684 void Curl_connect_free(struct Curl_easy *data)
685 {
686   (void)data;
687 }
688 
689 #endif /* CURL_DISABLE_PROXY */
690