1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2018, 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 conn->bits.close = TRUE; /* a failed connection is marked for closure to
62 prevent (bad) re-use or similar */
68 return CURLE_NOT_BUILT_IN;
72 CURLcode Curl_proxy_connect(struct connectdata *conn, int sockindex)
74 if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) {
75 const CURLcode result = https_proxy_connect(conn, sockindex);
78 if(!conn->bits.proxy_ssl_connected[sockindex])
79 return result; /* wait for HTTPS proxy SSL initialization to complete */
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;
92 /* We want "seamless" operations through HTTP proxy tunnel */
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
100 * This function might be called several times in the multi interface case
101 * if the proxy's CONNECT response is not instant.
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");
108 /* for the secondary socket (FTP), use the "connect to host"
109 * but ignore the "connect to port" (use the secondary port)
112 if(conn->bits.conn_to_host)
113 hostname = conn->conn_to_host.name;
114 else if(sockindex == SECONDARYSOCKET)
115 hostname = conn->secondaryhostname;
117 hostname = conn->host.name;
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;
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)
129 Curl_safefree(conn->allocptr.proxyuserpwd);
131 return CURLE_NOT_BUILT_IN;
134 /* no HTTP tunnel proxy, just return */
138 bool Curl_connect_complete(struct connectdata *conn)
140 return !conn->connect_state ||
141 (conn->connect_state->tunnel_state == TUNNEL_COMPLETE);
144 bool Curl_connect_ongoing(struct connectdata *conn)
146 return conn->connect_state &&
147 (conn->connect_state->tunnel_state != TUNNEL_COMPLETE);
150 static CURLcode connect_init(struct connectdata *conn, bool reinit)
152 struct http_connect_state *s;
154 DEBUGASSERT(!conn->connect_state);
155 s = calloc(1, sizeof(struct http_connect_state));
157 return CURLE_OUT_OF_MEMORY;
158 infof(conn->data, "allocate connect buffer!\n");
159 conn->connect_state = s;
162 DEBUGASSERT(conn->connect_state);
163 s = conn->connect_state;
165 s->tunnel_state = TUNNEL_INIT;
167 s->line_start = s->connect_buffer;
168 s->ptr = s->line_start;
170 s->close_connection = FALSE;
174 static void connect_done(struct connectdata *conn)
176 struct http_connect_state *s = conn->connect_state;
177 s->tunnel_state = TUNNEL_COMPLETE;
178 infof(conn->data, "CONNECT phase completed!\n");
181 static CURLcode CONNECT(struct connectdata *conn,
183 const char *hostname,
187 struct Curl_easy *data = conn->data;
188 struct SingleRequest *k = &data->req;
190 curl_socket_t tunnelsocket = conn->sock[sockindex];
192 struct http_connect_state *s = conn->connect_state;
195 #define SELECT_ERROR 1
196 #define SELECT_TIMEOUT 2
198 if(Curl_connect_complete(conn))
199 return CURLE_OK; /* CONNECT is already completed */
201 conn->bits.proxy_connect_closed = FALSE;
204 if(TUNNEL_INIT == s->tunnel_state) {
205 /* BEGIN CONNECT PHASE */
207 Curl_send_buffer *req_buffer;
209 infof(data, "Establish HTTP proxy tunnel to %s:%hu\n",
210 hostname, remote_port);
212 /* This only happens if we've looped here due to authentication
213 reasons, and we don't really use the newly cloned URL here
214 then. Just free() it. */
215 free(data->req.newurl);
216 data->req.newurl = NULL;
218 /* initialize a dynamic send-buffer */
219 req_buffer = Curl_add_buffer_init();
222 return CURLE_OUT_OF_MEMORY;
224 host_port = aprintf("%s:%hu", hostname, remote_port);
226 Curl_add_buffer_free(req_buffer);
227 return CURLE_OUT_OF_MEMORY;
230 /* Setup the proxy-authorization header, if any */
231 result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
237 const char *proxyconn = "";
238 const char *useragent = "";
239 const char *http = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ?
241 bool ipv6_ip = conn->bits.ipv6_ip;
244 /* the hostname may be different */
245 if(hostname != conn->host.name)
246 ipv6_ip = (strchr(hostname, ':') != NULL);
247 hostheader = /* host:port with IPv6 support */
248 aprintf("%s%s%s:%hu", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
251 Curl_add_buffer_free(req_buffer);
252 return CURLE_OUT_OF_MEMORY;
255 if(!Curl_checkProxyheaders(conn, "Host")) {
256 host = aprintf("Host: %s\r\n", hostheader);
259 Curl_add_buffer_free(req_buffer);
260 return CURLE_OUT_OF_MEMORY;
263 if(!Curl_checkProxyheaders(conn, "Proxy-Connection"))
264 proxyconn = "Proxy-Connection: Keep-Alive\r\n";
266 if(!Curl_checkProxyheaders(conn, "User-Agent") &&
267 data->set.str[STRING_USERAGENT])
268 useragent = conn->allocptr.uagent;
271 Curl_add_bufferf(req_buffer,
272 "CONNECT %s HTTP/%s\r\n"
274 "%s" /* Proxy-Authorization */
275 "%s" /* User-Agent */
276 "%s", /* Proxy-Connection */
280 conn->allocptr.proxyuserpwd?
281 conn->allocptr.proxyuserpwd:"",
290 result = Curl_add_custom_headers(conn, TRUE, req_buffer);
293 /* CRLF terminate the request */
294 result = Curl_add_bufferf(req_buffer, "\r\n");
297 /* Send the connect request to the proxy */
300 Curl_add_buffer_send(req_buffer, conn,
301 &data->info.request_size, 0, sockindex);
305 failf(data, "Failed sending CONNECT to proxy");
308 Curl_add_buffer_free(req_buffer);
312 s->tunnel_state = TUNNEL_CONNECT;
314 } /* END CONNECT PHASE */
316 check = Curl_timeleft(data, NULL, TRUE);
318 failf(data, "Proxy CONNECT aborted due to timeout");
319 return CURLE_OPERATION_TIMEDOUT;
322 if(!Curl_conn_data_pending(conn, sockindex))
323 /* return so we'll be called again polling-style */
326 /* at this point, the tunnel_connecting phase is over. */
328 { /* READING RESPONSE PHASE */
329 int error = SELECT_OK;
331 while(s->keepon && !error) {
334 /* make sure we have space to read more data */
335 if(s->ptr >= &s->connect_buffer[CONNECT_BUFFER_SIZE]) {
336 failf(data, "CONNECT response too large!");
337 return CURLE_RECV_ERROR;
340 /* Read one byte at a time to avoid a race condition. Wait at most one
341 second before looping to ensure continuous pgrsUpdates. */
342 result = Curl_read(conn, tunnelsocket, s->ptr, 1, &gotbytes);
343 if(result == CURLE_AGAIN)
344 /* socket buffer drained, return */
347 if(Curl_pgrsUpdate(conn))
348 return CURLE_ABORTED_BY_CALLBACK;
354 else if(gotbytes <= 0) {
355 if(data->set.proxyauth && data->state.authproxy.avail) {
356 /* proxy auth was requested and there was proxy auth available,
357 then deem this as "mere" proxy disconnect */
358 conn->bits.proxy_connect_closed = TRUE;
359 infof(data, "Proxy CONNECT connection closed\n");
362 error = SELECT_ERROR;
363 failf(data, "Proxy CONNECT aborted");
370 if(s->keepon > TRUE) {
371 /* This means we are currently ignoring a response-body */
373 s->ptr = s->connect_buffer;
375 /* A Content-Length based body: simply count down the counter
376 and make sure to break out of the loop when we're done! */
380 s->tunnel_state = TUNNEL_COMPLETE;
385 /* chunked-encoded body, so we need to do the chunked dance
386 properly to know when the end of the body is reached */
388 ssize_t tookcareof = 0;
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);
393 if(r == CHUNKE_STOP) {
394 /* we're done reading chunks! */
395 infof(data, "chunk reading DONE\n");
397 /* we did the full CONNECT treatment, go COMPLETE */
398 s->tunnel_state = TUNNEL_COMPLETE;
404 s->perline++; /* amount of bytes in this line so far */
406 /* if this is not the end of a header line then continue */
407 if(*s->ptr != 0x0a) {
412 /* convert from the network encoding */
413 result = Curl_convert_from_network(data, s->line_start,
415 /* Curl_convert_from_network calls failf if unsuccessful */
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, conn);
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;
430 result = Curl_client_write(conn, writetype,
431 s->line_start, s->perline);
436 data->info.header_size += (long)s->perline;
437 data->req.headerbytecount += (long)s->perline;
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.*/
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 */
454 infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
455 " bytes of response-body\n", s->cl);
457 else if(s->chunked_encoding) {
460 infof(data, "Ignore chunked response-body\n");
462 /* We set ignorebody true here since the chunked
463 decoder function will acknowledge that. Pay
464 attention so that this is cleared again when this
466 k->ignorebody = TRUE;
468 if(s->line_start[1] == '\n') {
469 /* this can only be a LF if the letter at index 0
474 /* now parse the chunked piece of data so that we can
475 properly tell when the stream ends */
476 r = Curl_httpchunk_read(conn, s->line_start + 1, 1, &gotbytes);
477 if(r == CHUNKE_STOP) {
478 /* we're done reading chunks! */
479 infof(data, "chunk reading DONE\n");
481 /* we did the full CONNECT treatment, go to COMPLETE */
482 s->tunnel_state = TUNNEL_COMPLETE;
486 /* without content-length or chunked encoding, we
487 can't keep the connection alive since the close is
488 the end signal so we bail out at once instead */
495 /* we did the full CONNECT treatment, go to COMPLETE */
496 s->tunnel_state = TUNNEL_COMPLETE;
500 s->line_start[s->perline] = 0; /* zero terminate the buffer */
501 if((checkprefix("WWW-Authenticate:", s->line_start) &&
502 (401 == k->httpcode)) ||
503 (checkprefix("Proxy-authenticate:", s->line_start) &&
504 (407 == k->httpcode))) {
506 bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
507 char *auth = Curl_copy_header_value(s->line_start);
509 return CURLE_OUT_OF_MEMORY;
511 result = Curl_http_input_auth(conn, proxy, auth);
518 else if(checkprefix("Content-Length:", s->line_start)) {
519 if(k->httpcode/100 == 2) {
520 /* A client MUST ignore any Content-Length or Transfer-Encoding
521 header fields received in a successful response to CONNECT.
522 "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
523 infof(data, "Ignoring Content-Length in CONNECT %03d response\n",
527 (void)curlx_strtoofft(s->line_start +
528 strlen("Content-Length:"), NULL, 10, &s->cl);
531 else if(Curl_compareheader(s->line_start, "Connection:", "close"))
532 s->close_connection = TRUE;
533 else if(checkprefix("Transfer-Encoding:", s->line_start)) {
534 if(k->httpcode/100 == 2) {
535 /* A client MUST ignore any Content-Length or Transfer-Encoding
536 header fields received in a successful response to CONNECT.
537 "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
538 infof(data, "Ignoring Transfer-Encoding in "
539 "CONNECT %03d response\n", k->httpcode);
541 else if(Curl_compareheader(s->line_start,
542 "Transfer-Encoding:", "chunked")) {
543 infof(data, "CONNECT responded chunked\n");
544 s->chunked_encoding = TRUE;
545 /* init our chunky engine */
546 Curl_httpchunk_init(conn);
549 else if(Curl_compareheader(s->line_start,
550 "Proxy-Connection:", "close"))
551 s->close_connection = TRUE;
552 else if(2 == sscanf(s->line_start, "HTTP/1.%d %d",
555 /* store the HTTP code from the proxy */
556 data->info.httpproxycode = k->httpcode;
559 s->perline = 0; /* line starts over here */
560 s->ptr = s->connect_buffer;
561 s->line_start = s->ptr;
562 } /* while there's buffer left and loop is requested */
564 if(Curl_pgrsUpdate(conn))
565 return CURLE_ABORTED_BY_CALLBACK;
568 return CURLE_RECV_ERROR;
570 if(data->info.httpproxycode/100 != 2) {
571 /* Deal with the possibly already received authenticate
572 headers. 'newurl' is set to a new URL if we must loop. */
573 result = Curl_http_auth_act(conn);
578 /* the connection has been marked for closure, most likely in the
579 Curl_http_auth_act() function and thus we can kill it at once
581 s->close_connection = TRUE;
584 if(s->close_connection && data->req.newurl) {
585 /* Connection closed by server. Don't use it anymore */
586 Curl_closesocket(conn, conn->sock[sockindex]);
587 conn->sock[sockindex] = CURL_SOCKET_BAD;
590 } /* END READING RESPONSE PHASE */
592 /* If we are supposed to continue and request a new URL, which basically
593 * means the HTTP authentication is still going on so if the tunnel
594 * is complete we start over in INIT state */
595 if(data->req.newurl && (TUNNEL_COMPLETE == s->tunnel_state)) {
596 connect_init(conn, TRUE); /* reinit */
599 } while(data->req.newurl);
601 if(data->info.httpproxycode/100 != 2) {
602 if(s->close_connection && data->req.newurl) {
603 conn->bits.proxy_connect_closed = TRUE;
604 infof(data, "Connect me again please\n");
608 free(data->req.newurl);
609 data->req.newurl = NULL;
610 /* failure, close this connection to avoid re-use */
611 streamclose(conn, "proxy CONNECT failure");
612 Curl_closesocket(conn, conn->sock[sockindex]);
613 conn->sock[sockindex] = CURL_SOCKET_BAD;
616 /* to back to init state */
617 s->tunnel_state = TUNNEL_INIT;
619 if(conn->bits.proxy_connect_closed)
620 /* this is not an error, just part of the connection negotiation */
622 failf(data, "Received HTTP code %d from proxy after CONNECT",
624 return CURLE_RECV_ERROR;
627 s->tunnel_state = TUNNEL_COMPLETE;
629 /* If a proxy-authorization header was used for the proxy, then we should
630 make sure that it isn't accidentally used for the document request
631 after we've connected. So let's free and clear it here. */
632 Curl_safefree(conn->allocptr.proxyuserpwd);
633 conn->allocptr.proxyuserpwd = NULL;
635 data->state.authproxy.done = TRUE;
637 infof(data, "Proxy replied %d to CONNECT request\n",
638 data->info.httpproxycode);
639 data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
640 conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the
645 void Curl_connect_free(struct Curl_easy *data)
647 struct connectdata *conn = data->easy_conn;
648 struct http_connect_state *s = conn->connect_state;
651 conn->connect_state = NULL;
656 * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
657 * function will issue the necessary commands to get a seamless tunnel through
658 * this proxy. After that, the socket can be used just as a normal socket.
661 CURLcode Curl_proxyCONNECT(struct connectdata *conn,
663 const char *hostname,
667 if(!conn->connect_state) {
668 result = connect_init(conn, FALSE);
672 result = CONNECT(conn, sockindex, hostname, remote_port);
674 if(result || Curl_connect_complete(conn))
681 void Curl_connect_free(struct Curl_easy *data)
686 #endif /* CURL_DISABLE_PROXY */