• 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 && !error) {
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             ssize_t tookcareof = 0;
388 
389             /* now parse the chunked piece of data so that we can
390                properly tell when the stream ends */
391             r = Curl_httpchunk_read(conn, s->ptr, 1, &tookcareof);
392             if(r == CHUNKE_STOP) {
393               /* we're done reading chunks! */
394               infof(data, "chunk reading DONE\n");
395               s->keepon = FALSE;
396               /* we did the full CONNECT treatment, go COMPLETE */
397               s->tunnel_state = TUNNEL_COMPLETE;
398             }
399           }
400           continue;
401         }
402 
403         s->perline++; /* amount of bytes in this line so far */
404 
405         /* if this is not the end of a header line then continue */
406         if(*s->ptr != 0x0a) {
407           s->ptr++;
408           continue;
409         }
410 
411         /* convert from the network encoding */
412         result = Curl_convert_from_network(data, s->line_start,
413                                            (size_t)s->perline);
414         /* Curl_convert_from_network calls failf if unsuccessful */
415         if(result)
416           return result;
417 
418         /* output debug if that is requested */
419         if(data->set.verbose)
420           Curl_debug(data, CURLINFO_HEADER_IN,
421                      s->line_start, (size_t)s->perline);
422 
423         if(!data->set.suppress_connect_headers) {
424           /* send the header to the callback */
425           int writetype = CLIENTWRITE_HEADER;
426           if(data->set.include_header)
427             writetype |= CLIENTWRITE_BODY;
428 
429           result = Curl_client_write(conn, writetype,
430                                      s->line_start, s->perline);
431           if(result)
432             return result;
433         }
434 
435         data->info.header_size += (long)s->perline;
436         data->req.headerbytecount += (long)s->perline;
437 
438         /* Newlines are CRLF, so the CR is ignored as the line isn't
439            really terminated until the LF comes. Treat a following CR
440            as end-of-headers as well.*/
441 
442         if(('\r' == s->line_start[0]) ||
443            ('\n' == s->line_start[0])) {
444           /* end of response-headers from the proxy */
445           s->ptr = s->connect_buffer;
446           if((407 == k->httpcode) && !data->state.authproblem) {
447             /* If we get a 407 response code with content length
448                when we have no auth problem, we must ignore the
449                whole response-body */
450             s->keepon = 2;
451 
452             if(s->cl) {
453               infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
454                     " bytes of response-body\n", s->cl);
455             }
456             else if(s->chunked_encoding) {
457               CHUNKcode r;
458 
459               infof(data, "Ignore chunked response-body\n");
460 
461               /* We set ignorebody true here since the chunked
462                  decoder function will acknowledge that. Pay
463                  attention so that this is cleared again when this
464                  function returns! */
465               k->ignorebody = TRUE;
466 
467               if(s->line_start[1] == '\n') {
468                 /* this can only be a LF if the letter at index 0
469                    was a CR */
470                 s->line_start++;
471               }
472 
473               /* now parse the chunked piece of data so that we can
474                  properly tell when the stream ends */
475               r = Curl_httpchunk_read(conn, s->line_start + 1, 1, &gotbytes);
476               if(r == CHUNKE_STOP) {
477                 /* we're done reading chunks! */
478                 infof(data, "chunk reading DONE\n");
479                 s->keepon = FALSE;
480                 /* we did the full CONNECT treatment, go to COMPLETE */
481                 s->tunnel_state = TUNNEL_COMPLETE;
482               }
483             }
484             else {
485               /* without content-length or chunked encoding, we
486                  can't keep the connection alive since the close is
487                  the end signal so we bail out at once instead */
488               s->keepon = FALSE;
489             }
490           }
491           else
492             s->keepon = FALSE;
493           if(!s->cl)
494             /* we did the full CONNECT treatment, go to COMPLETE */
495             s->tunnel_state = TUNNEL_COMPLETE;
496           continue;
497         }
498 
499         s->line_start[s->perline] = 0; /* zero terminate the buffer */
500         if((checkprefix("WWW-Authenticate:", s->line_start) &&
501             (401 == k->httpcode)) ||
502            (checkprefix("Proxy-authenticate:", s->line_start) &&
503             (407 == k->httpcode))) {
504 
505           bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
506           char *auth = Curl_copy_header_value(s->line_start);
507           if(!auth)
508             return CURLE_OUT_OF_MEMORY;
509 
510           result = Curl_http_input_auth(conn, proxy, auth);
511 
512           free(auth);
513 
514           if(result)
515             return result;
516         }
517         else if(checkprefix("Content-Length:", s->line_start)) {
518           if(k->httpcode/100 == 2) {
519             /* A client MUST ignore any Content-Length or Transfer-Encoding
520                header fields received in a successful response to CONNECT.
521                "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
522             infof(data, "Ignoring Content-Length in CONNECT %03d response\n",
523                   k->httpcode);
524           }
525           else {
526             (void)curlx_strtoofft(s->line_start +
527                                   strlen("Content-Length:"), NULL, 10, &s->cl);
528           }
529         }
530         else if(Curl_compareheader(s->line_start, "Connection:", "close"))
531           s->close_connection = TRUE;
532         else if(checkprefix("Transfer-Encoding:", s->line_start)) {
533           if(k->httpcode/100 == 2) {
534             /* A client MUST ignore any Content-Length or Transfer-Encoding
535                header fields received in a successful response to CONNECT.
536                "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
537             infof(data, "Ignoring Transfer-Encoding in "
538                   "CONNECT %03d response\n", k->httpcode);
539           }
540           else if(Curl_compareheader(s->line_start,
541                                      "Transfer-Encoding:", "chunked")) {
542             infof(data, "CONNECT responded chunked\n");
543             s->chunked_encoding = TRUE;
544             /* init our chunky engine */
545             Curl_httpchunk_init(conn);
546           }
547         }
548         else if(Curl_compareheader(s->line_start,
549                                    "Proxy-Connection:", "close"))
550           s->close_connection = TRUE;
551         else if(2 == sscanf(s->line_start, "HTTP/1.%d %d",
552                             &subversion,
553                             &k->httpcode)) {
554           /* store the HTTP code from the proxy */
555           data->info.httpproxycode = k->httpcode;
556         }
557 
558         s->perline = 0; /* line starts over here */
559         s->ptr = s->connect_buffer;
560         s->line_start = s->ptr;
561       } /* while there's buffer left and loop is requested */
562 
563       if(Curl_pgrsUpdate(conn))
564         return CURLE_ABORTED_BY_CALLBACK;
565 
566       if(error)
567         return CURLE_RECV_ERROR;
568 
569       if(data->info.httpproxycode/100 != 2) {
570         /* Deal with the possibly already received authenticate
571            headers. 'newurl' is set to a new URL if we must loop. */
572         result = Curl_http_auth_act(conn);
573         if(result)
574           return result;
575 
576         if(conn->bits.close)
577           /* the connection has been marked for closure, most likely in the
578              Curl_http_auth_act() function and thus we can kill it at once
579              below */
580           s->close_connection = TRUE;
581       }
582 
583       if(s->close_connection && data->req.newurl) {
584         /* Connection closed by server. Don't use it anymore */
585         Curl_closesocket(conn, conn->sock[sockindex]);
586         conn->sock[sockindex] = CURL_SOCKET_BAD;
587         break;
588       }
589     } /* END READING RESPONSE PHASE */
590 
591     /* If we are supposed to continue and request a new URL, which basically
592      * means the HTTP authentication is still going on so if the tunnel
593      * is complete we start over in INIT state */
594     if(data->req.newurl && (TUNNEL_COMPLETE == s->tunnel_state)) {
595       connect_init(conn, TRUE); /* reinit */
596     }
597 
598   } while(data->req.newurl);
599 
600   if(data->info.httpproxycode/100 != 2) {
601     if(s->close_connection && data->req.newurl) {
602       conn->bits.proxy_connect_closed = TRUE;
603       infof(data, "Connect me again please\n");
604       connect_done(conn);
605     }
606     else {
607       free(data->req.newurl);
608       data->req.newurl = NULL;
609       /* failure, close this connection to avoid re-use */
610       streamclose(conn, "proxy CONNECT failure");
611       Curl_closesocket(conn, conn->sock[sockindex]);
612       conn->sock[sockindex] = CURL_SOCKET_BAD;
613     }
614 
615     /* to back to init state */
616     s->tunnel_state = TUNNEL_INIT;
617 
618     if(conn->bits.proxy_connect_closed)
619       /* this is not an error, just part of the connection negotiation */
620       return CURLE_OK;
621     failf(data, "Received HTTP code %d from proxy after CONNECT",
622           data->req.httpcode);
623     return CURLE_RECV_ERROR;
624   }
625 
626   s->tunnel_state = TUNNEL_COMPLETE;
627 
628   /* If a proxy-authorization header was used for the proxy, then we should
629      make sure that it isn't accidentally used for the document request
630      after we've connected. So let's free and clear it here. */
631   Curl_safefree(conn->allocptr.proxyuserpwd);
632   conn->allocptr.proxyuserpwd = NULL;
633 
634   data->state.authproxy.done = TRUE;
635 
636   infof(data, "Proxy replied %d to CONNECT request\n",
637         data->info.httpproxycode);
638   data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
639   conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the
640                                          document request  */
641   return CURLE_OK;
642 }
643 
Curl_connect_free(struct Curl_easy * data)644 void Curl_connect_free(struct Curl_easy *data)
645 {
646   struct connectdata *conn = data->conn;
647   struct http_connect_state *s = conn->connect_state;
648   if(s) {
649     free(s);
650     conn->connect_state = NULL;
651   }
652 }
653 
654 /*
655  * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
656  * function will issue the necessary commands to get a seamless tunnel through
657  * this proxy. After that, the socket can be used just as a normal socket.
658  */
659 
Curl_proxyCONNECT(struct connectdata * conn,int sockindex,const char * hostname,int remote_port)660 CURLcode Curl_proxyCONNECT(struct connectdata *conn,
661                            int sockindex,
662                            const char *hostname,
663                            int remote_port)
664 {
665   CURLcode result;
666   if(!conn->connect_state) {
667     result = connect_init(conn, FALSE);
668     if(result)
669       return result;
670   }
671   result = CONNECT(conn, sockindex, hostname, remote_port);
672 
673   if(result || Curl_connect_complete(conn))
674     connect_done(conn);
675 
676   return result;
677 }
678 
679 #else
Curl_connect_free(struct Curl_easy * data)680 void Curl_connect_free(struct Curl_easy *data)
681 {
682   (void)data;
683 }
684 
685 #endif /* CURL_DISABLE_PROXY */
686