1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
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.
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.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 ***************************************************************************/
23 #include "curl_setup.h"
25 #include "http_proxy.h"
27 #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
29 #include <curl/curl.h>
35 #include "non-ascii.h"
38 #include "vtls/vtls.h"
40 /* The last 3 #include files should be in this order */
41 #include "curl_printf.h"
42 #include "curl_memory.h"
46 * Perform SSL initialization for HTTPS proxy. Sets
47 * proxy_ssl_connected connection bit when complete. Can be
48 * called multiple times.
50 static CURLcode https_proxy_connect(struct connectdata *conn, int sockindex)
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 */
58 Curl_ssl_connect_nonblocking(conn, sockindex,
59 &conn->bits.proxy_ssl_connected[sockindex]);
61 /* a failed connection is marked for closure to prevent (bad) re-use or
63 connclose(conn, "TLS handshake failed");
69 return CURLE_NOT_BUILT_IN;
73 CURLcode Curl_proxy_connect(struct connectdata *conn, int sockindex)
75 struct Curl_easy *data = conn->data;
76 if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) {
77 const CURLcode result = https_proxy_connect(conn, sockindex);
80 if(!conn->bits.proxy_ssl_connected[sockindex])
81 return result; /* wait for HTTPS proxy SSL initialization to complete */
84 if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
85 #ifndef CURL_DISABLE_PROXY
86 /* for [protocol] tunneled through HTTP proxy */
87 struct HTTP http_proxy;
94 /* We want "seamless" operations through HTTP proxy tunnel */
96 /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the
97 * member conn->proto.http; we want [protocol] through HTTP and we have
98 * to change the member temporarily for connecting to the HTTP
99 * proxy. After Curl_proxyCONNECT we have to set back the member to the
102 * This function might be called several times in the multi interface case
103 * if the proxy's CONNECT response is not instant.
105 prot_save = conn->data->req.protop;
106 memset(&http_proxy, 0, sizeof(http_proxy));
107 conn->data->req.protop = &http_proxy;
108 connkeep(conn, "HTTP proxy CONNECT");
110 /* for the secondary socket (FTP), use the "connect to host"
111 * but ignore the "connect to port" (use the secondary port)
114 if(conn->bits.conn_to_host)
115 hostname = conn->conn_to_host.name;
116 else if(sockindex == SECONDARYSOCKET)
117 hostname = conn->secondaryhostname;
119 hostname = conn->host.name;
121 if(sockindex == SECONDARYSOCKET)
122 remote_port = conn->secondary_port;
123 else if(conn->bits.conn_to_port)
124 remote_port = conn->conn_to_port;
126 remote_port = conn->remote_port;
127 result = Curl_proxyCONNECT(conn, sockindex, hostname, remote_port);
128 conn->data->req.protop = prot_save;
129 if(CURLE_OK != result)
131 Curl_safefree(data->state.aptr.proxyuserpwd);
133 return CURLE_NOT_BUILT_IN;
136 /* no HTTP tunnel proxy, just return */
140 bool Curl_connect_complete(struct connectdata *conn)
142 return !conn->connect_state ||
143 (conn->connect_state->tunnel_state == TUNNEL_COMPLETE);
146 bool Curl_connect_ongoing(struct connectdata *conn)
148 return conn->connect_state &&
149 (conn->connect_state->tunnel_state != TUNNEL_COMPLETE);
152 static CURLcode connect_init(struct connectdata *conn, bool reinit)
154 struct http_connect_state *s;
156 DEBUGASSERT(!conn->connect_state);
157 s = calloc(1, sizeof(struct http_connect_state));
159 return CURLE_OUT_OF_MEMORY;
160 infof(conn->data, "allocate connect buffer!\n");
161 conn->connect_state = s;
162 Curl_dyn_init(&s->rcvbuf, DYN_PROXY_CONNECT_HEADERS);
165 DEBUGASSERT(conn->connect_state);
166 s = conn->connect_state;
167 Curl_dyn_reset(&s->rcvbuf);
169 s->tunnel_state = TUNNEL_INIT;
172 s->close_connection = FALSE;
176 static void connect_done(struct connectdata *conn)
178 struct http_connect_state *s = conn->connect_state;
179 s->tunnel_state = TUNNEL_COMPLETE;
180 Curl_dyn_free(&s->rcvbuf);
181 infof(conn->data, "CONNECT phase completed!\n");
184 static CURLcode CONNECT(struct connectdata *conn,
186 const char *hostname,
190 struct Curl_easy *data = conn->data;
191 struct SingleRequest *k = &data->req;
193 curl_socket_t tunnelsocket = conn->sock[sockindex];
194 struct http_connect_state *s = conn->connect_state;
199 #define SELECT_ERROR 1
201 if(Curl_connect_complete(conn))
202 return CURLE_OK; /* CONNECT is already completed */
204 conn->bits.proxy_connect_closed = FALSE;
208 if(TUNNEL_INIT == s->tunnel_state) {
209 /* BEGIN CONNECT PHASE */
213 infof(data, "Establish HTTP proxy tunnel to %s:%d\n",
214 hostname, remote_port);
216 /* This only happens if we've looped here due to authentication
217 reasons, and we don't really use the newly cloned URL here
218 then. Just free() it. */
219 free(data->req.newurl);
220 data->req.newurl = NULL;
222 host_port = aprintf("%s:%d", hostname, remote_port);
224 return CURLE_OUT_OF_MEMORY;
226 /* initialize a dynamic send-buffer */
227 Curl_dyn_init(&req, DYN_HTTP_REQUEST);
229 /* Setup the proxy-authorization header, if any */
230 result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
236 const char *proxyconn = "";
237 const char *useragent = "";
239 (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? "1.0" : "1.1";
240 bool ipv6_ip = conn->bits.ipv6_ip;
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?"]":"",
251 return CURLE_OUT_OF_MEMORY;
254 if(!Curl_checkProxyheaders(conn, "Host")) {
255 host = aprintf("Host: %s\r\n", hostheader);
259 return CURLE_OUT_OF_MEMORY;
262 if(!Curl_checkProxyheaders(conn, "Proxy-Connection"))
263 proxyconn = "Proxy-Connection: Keep-Alive\r\n";
265 if(!Curl_checkProxyheaders(conn, "User-Agent") &&
266 data->set.str[STRING_USERAGENT])
267 useragent = data->state.aptr.uagent;
271 "CONNECT %s HTTP/%s\r\n"
273 "%s" /* Proxy-Authorization */
274 "%s" /* User-Agent */
275 "%s", /* Proxy-Connection */
279 data->state.aptr.proxyuserpwd?
280 data->state.aptr.proxyuserpwd:"",
289 result = Curl_add_custom_headers(conn, TRUE, &req);
292 /* CRLF terminate the request */
293 result = Curl_dyn_add(&req, "\r\n");
296 /* Send the connect request to the proxy */
298 result = Curl_buffer_send(&req, conn, &data->info.request_size, 0,
302 failf(data, "Failed sending CONNECT to proxy");
309 s->tunnel_state = TUNNEL_CONNECT;
310 } /* END CONNECT PHASE */
312 check = Curl_timeleft(data, NULL, TRUE);
314 failf(data, "Proxy CONNECT aborted due to timeout");
315 return CURLE_OPERATION_TIMEDOUT;
318 if(!Curl_conn_data_pending(conn, sockindex))
319 /* return so we'll be called again polling-style */
322 /* at this point, the tunnel_connecting phase is over. */
324 { /* READING RESPONSE PHASE */
325 int error = SELECT_OK;
331 /* Read one byte at a time to avoid a race condition. Wait at most one
332 second before looping to ensure continuous pgrsUpdates. */
333 result = Curl_read(conn, tunnelsocket, &byte, 1, &gotbytes);
334 if(result == CURLE_AGAIN)
335 /* socket buffer drained, return */
338 if(Curl_pgrsUpdate(conn))
339 return CURLE_ABORTED_BY_CALLBACK;
345 else if(gotbytes <= 0) {
346 if(data->set.proxyauth && data->state.authproxy.avail) {
347 /* proxy auth was requested and there was proxy auth available,
348 then deem this as "mere" proxy disconnect */
349 conn->bits.proxy_connect_closed = TRUE;
350 infof(data, "Proxy CONNECT connection closed\n");
353 error = SELECT_ERROR;
354 failf(data, "Proxy CONNECT aborted");
360 if(s->keepon > TRUE) {
361 /* This means we are currently ignoring a response-body */
364 /* A Content-Length based body: simply count down the counter
365 and make sure to break out of the loop when we're done! */
369 s->tunnel_state = TUNNEL_COMPLETE;
374 /* chunked-encoded body, so we need to do the chunked dance
375 properly to know when the end of the body is reached */
378 ssize_t tookcareof = 0;
380 /* now parse the chunked piece of data so that we can
381 properly tell when the stream ends */
382 r = Curl_httpchunk_read(conn, &byte, 1, &tookcareof, &extra);
383 if(r == CHUNKE_STOP) {
384 /* we're done reading chunks! */
385 infof(data, "chunk reading DONE\n");
387 /* we did the full CONNECT treatment, go COMPLETE */
388 s->tunnel_state = TUNNEL_COMPLETE;
394 if(Curl_dyn_addn(&s->rcvbuf, &byte, 1)) {
395 failf(data, "CONNECT response too large!");
396 return CURLE_RECV_ERROR;
399 /* if this is not the end of a header line then continue */
403 linep = Curl_dyn_ptr(&s->rcvbuf);
404 perline = Curl_dyn_len(&s->rcvbuf); /* amount of bytes in this line */
406 /* convert from the network encoding */
407 result = Curl_convert_from_network(data, linep, perline);
408 /* Curl_convert_from_network calls failf if unsuccessful */
412 /* output debug if that is requested */
413 if(data->set.verbose)
414 Curl_debug(data, CURLINFO_HEADER_IN, linep, perline);
416 if(!data->set.suppress_connect_headers) {
417 /* send the header to the callback */
418 int writetype = CLIENTWRITE_HEADER;
419 if(data->set.include_header)
420 writetype |= CLIENTWRITE_BODY;
422 result = Curl_client_write(conn, writetype, linep, perline);
427 data->info.header_size += (long)perline;
429 /* Newlines are CRLF, so the CR is ignored as the line isn't
430 really terminated until the LF comes. Treat a following CR
431 as end-of-headers as well.*/
433 if(('\r' == linep[0]) ||
434 ('\n' == linep[0])) {
435 /* end of response-headers from the proxy */
437 if((407 == k->httpcode) && !data->state.authproblem) {
438 /* If we get a 407 response code with content length
439 when we have no auth problem, we must ignore the
440 whole response-body */
444 infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
445 " bytes of response-body\n", s->cl);
447 else if(s->chunked_encoding) {
451 infof(data, "Ignore chunked response-body\n");
453 /* We set ignorebody true here since the chunked decoder
454 function will acknowledge that. Pay attention so that this is
455 cleared again when this function returns! */
456 k->ignorebody = TRUE;
459 /* this can only be a LF if the letter at index 0 was a CR */
462 /* now parse the chunked piece of data so that we can properly
463 tell when the stream ends */
464 r = Curl_httpchunk_read(conn, linep + 1, 1, &gotbytes,
466 if(r == CHUNKE_STOP) {
467 /* we're done reading chunks! */
468 infof(data, "chunk reading DONE\n");
470 /* we did the full CONNECT treatment, go to COMPLETE */
471 s->tunnel_state = TUNNEL_COMPLETE;
475 /* without content-length or chunked encoding, we
476 can't keep the connection alive since the close is
477 the end signal so we bail out at once instead */
484 /* we did the full CONNECT treatment, go to COMPLETE */
485 s->tunnel_state = TUNNEL_COMPLETE;
489 if((checkprefix("WWW-Authenticate:", linep) &&
490 (401 == k->httpcode)) ||
491 (checkprefix("Proxy-authenticate:", linep) &&
492 (407 == k->httpcode))) {
494 bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
495 char *auth = Curl_copy_header_value(linep);
497 return CURLE_OUT_OF_MEMORY;
499 result = Curl_http_input_auth(conn, proxy, auth);
506 else if(checkprefix("Content-Length:", linep)) {
507 if(k->httpcode/100 == 2) {
508 /* A client MUST ignore any Content-Length or Transfer-Encoding
509 header fields received in a successful response to CONNECT.
510 "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
511 infof(data, "Ignoring Content-Length in CONNECT %03d response\n",
515 (void)curlx_strtoofft(linep +
516 strlen("Content-Length:"), NULL, 10, &s->cl);
519 else if(Curl_compareheader(linep, "Connection:", "close"))
520 s->close_connection = TRUE;
521 else if(checkprefix("Transfer-Encoding:", linep)) {
522 if(k->httpcode/100 == 2) {
523 /* A client MUST ignore any Content-Length or Transfer-Encoding
524 header fields received in a successful response to CONNECT.
525 "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
526 infof(data, "Ignoring Transfer-Encoding in "
527 "CONNECT %03d response\n", k->httpcode);
529 else if(Curl_compareheader(linep,
530 "Transfer-Encoding:", "chunked")) {
531 infof(data, "CONNECT responded chunked\n");
532 s->chunked_encoding = TRUE;
533 /* init our chunky engine */
534 Curl_httpchunk_init(conn);
537 else if(Curl_compareheader(linep, "Proxy-Connection:", "close"))
538 s->close_connection = TRUE;
539 else if(2 == sscanf(linep, "HTTP/1.%d %d",
542 /* store the HTTP code from the proxy */
543 data->info.httpproxycode = k->httpcode;
546 Curl_dyn_reset(&s->rcvbuf);
547 } /* while there's buffer left and loop is requested */
549 if(Curl_pgrsUpdate(conn))
550 return CURLE_ABORTED_BY_CALLBACK;
553 return CURLE_RECV_ERROR;
555 if(data->info.httpproxycode/100 != 2) {
556 /* Deal with the possibly already received authenticate
557 headers. 'newurl' is set to a new URL if we must loop. */
558 result = Curl_http_auth_act(conn);
563 /* the connection has been marked for closure, most likely in the
564 Curl_http_auth_act() function and thus we can kill it at once
566 s->close_connection = TRUE;
569 if(s->close_connection && data->req.newurl) {
570 /* Connection closed by server. Don't use it anymore */
571 Curl_closesocket(conn, conn->sock[sockindex]);
572 conn->sock[sockindex] = CURL_SOCKET_BAD;
575 } /* END READING RESPONSE PHASE */
577 /* If we are supposed to continue and request a new URL, which basically
578 * means the HTTP authentication is still going on so if the tunnel
579 * is complete we start over in INIT state */
580 if(data->req.newurl && (TUNNEL_COMPLETE == s->tunnel_state)) {
581 connect_init(conn, TRUE); /* reinit */
584 } while(data->req.newurl);
586 if(data->info.httpproxycode/100 != 2) {
587 if(s->close_connection && data->req.newurl) {
588 conn->bits.proxy_connect_closed = TRUE;
589 infof(data, "Connect me again please\n");
593 free(data->req.newurl);
594 data->req.newurl = NULL;
595 /* failure, close this connection to avoid re-use */
596 streamclose(conn, "proxy CONNECT failure");
597 Curl_closesocket(conn, conn->sock[sockindex]);
598 conn->sock[sockindex] = CURL_SOCKET_BAD;
601 /* to back to init state */
602 s->tunnel_state = TUNNEL_INIT;
604 if(conn->bits.proxy_connect_closed)
605 /* this is not an error, just part of the connection negotiation */
607 Curl_dyn_free(&s->rcvbuf);
608 failf(data, "Received HTTP code %d from proxy after CONNECT",
610 return CURLE_RECV_ERROR;
613 s->tunnel_state = TUNNEL_COMPLETE;
615 /* If a proxy-authorization header was used for the proxy, then we should
616 make sure that it isn't accidentally used for the document request
617 after we've connected. So let's free and clear it here. */
618 Curl_safefree(data->state.aptr.proxyuserpwd);
619 data->state.aptr.proxyuserpwd = NULL;
621 data->state.authproxy.done = TRUE;
622 data->state.authproxy.multipass = FALSE;
624 infof(data, "Proxy replied %d to CONNECT request\n",
625 data->info.httpproxycode);
626 data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
627 conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the
629 Curl_dyn_free(&s->rcvbuf);
633 void Curl_connect_free(struct Curl_easy *data)
635 struct connectdata *conn = data->conn;
636 struct http_connect_state *s = conn->connect_state;
639 conn->connect_state = NULL;
644 * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
645 * function will issue the necessary commands to get a seamless tunnel through
646 * this proxy. After that, the socket can be used just as a normal socket.
649 CURLcode Curl_proxyCONNECT(struct connectdata *conn,
651 const char *hostname,
655 if(!conn->connect_state) {
656 result = connect_init(conn, FALSE);
660 result = CONNECT(conn, sockindex, hostname, remote_port);
662 if(result || Curl_connect_complete(conn))
669 void Curl_connect_free(struct Curl_easy *data)
674 #endif /* CURL_DISABLE_PROXY */