1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2017, 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 #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
28 #include <curl/curl.h>
29 #include "http_proxy.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,
127 conn->data->req.protop = prot_save;
128 if(CURLE_OK != result)
130 Curl_safefree(conn->allocptr.proxyuserpwd);
132 return CURLE_NOT_BUILT_IN;
135 /* no HTTP tunnel proxy, just return */
140 * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
141 * function will issue the necessary commands to get a seamless tunnel through
142 * this proxy. After that, the socket can be used just as a normal socket.
144 * 'blocking' set to TRUE means that this function will do the entire CONNECT
145 * + response in a blocking fashion. Should be avoided!
148 CURLcode Curl_proxyCONNECT(struct connectdata *conn,
150 const char *hostname,
155 struct Curl_easy *data=conn->data;
156 struct SingleRequest *k = &data->req;
158 curl_socket_t tunnelsocket = conn->sock[sockindex];
160 bool closeConnection = FALSE;
161 bool chunked_encoding = FALSE;
165 #define SELECT_ERROR 1
166 #define SELECT_TIMEOUT 2
167 int error = SELECT_OK;
169 if(conn->tunnel_state[sockindex] == TUNNEL_COMPLETE)
170 return CURLE_OK; /* CONNECT is already completed */
172 conn->bits.proxy_connect_closed = FALSE;
175 if(TUNNEL_INIT == conn->tunnel_state[sockindex]) {
176 /* BEGIN CONNECT PHASE */
178 Curl_send_buffer *req_buffer;
180 infof(data, "Establish HTTP proxy tunnel to %s:%hu\n",
181 hostname, remote_port);
183 /* This only happens if we've looped here due to authentication
184 reasons, and we don't really use the newly cloned URL here
185 then. Just free() it. */
186 free(data->req.newurl);
187 data->req.newurl = NULL;
189 /* initialize a dynamic send-buffer */
190 req_buffer = Curl_add_buffer_init();
193 return CURLE_OUT_OF_MEMORY;
195 host_port = aprintf("%s:%hu", hostname, remote_port);
197 Curl_add_buffer_free(req_buffer);
198 return CURLE_OUT_OF_MEMORY;
201 /* Setup the proxy-authorization header, if any */
202 result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
208 const char *proxyconn="";
209 const char *useragent="";
210 const char *http = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ?
212 bool ipv6_ip = conn->bits.ipv6_ip;
215 /* the hostname may be different */
216 if(hostname != conn->host.name)
217 ipv6_ip = (strchr(hostname, ':') != NULL);
218 hostheader= /* host:port with IPv6 support */
219 aprintf("%s%s%s:%hu", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
222 Curl_add_buffer_free(req_buffer);
223 return CURLE_OUT_OF_MEMORY;
226 if(!Curl_checkProxyheaders(conn, "Host:")) {
227 host = aprintf("Host: %s\r\n", hostheader);
230 Curl_add_buffer_free(req_buffer);
231 return CURLE_OUT_OF_MEMORY;
234 if(!Curl_checkProxyheaders(conn, "Proxy-Connection:"))
235 proxyconn = "Proxy-Connection: Keep-Alive\r\n";
237 if(!Curl_checkProxyheaders(conn, "User-Agent:") &&
238 data->set.str[STRING_USERAGENT])
239 useragent = conn->allocptr.uagent;
242 Curl_add_bufferf(req_buffer,
243 "CONNECT %s HTTP/%s\r\n"
245 "%s" /* Proxy-Authorization */
246 "%s" /* User-Agent */
247 "%s", /* Proxy-Connection */
251 conn->allocptr.proxyuserpwd?
252 conn->allocptr.proxyuserpwd:"",
261 result = Curl_add_custom_headers(conn, TRUE, req_buffer);
264 /* CRLF terminate the request */
265 result = Curl_add_bufferf(req_buffer, "\r\n");
268 /* Send the connect request to the proxy */
271 Curl_add_buffer_send(req_buffer, conn,
272 &data->info.request_size, 0, sockindex);
276 failf(data, "Failed sending CONNECT to proxy");
279 Curl_add_buffer_free(req_buffer);
283 conn->tunnel_state[sockindex] = TUNNEL_CONNECT;
284 } /* END CONNECT PHASE */
286 check = Curl_timeleft(data, NULL, TRUE);
288 failf(data, "Proxy CONNECT aborted due to timeout");
289 return CURLE_RECV_ERROR;
293 if(!Curl_conn_data_pending(conn, sockindex))
294 /* return so we'll be called again polling-style */
298 "Read response immediately from proxy CONNECT\n"));
302 /* at this point, the tunnel_connecting phase is over. */
304 { /* READING RESPONSE PHASE */
305 size_t nread; /* total size read */
306 int perline; /* count bytes per line */
312 ptr = data->state.buffer;
318 while(nread < BUFSIZE && keepon && !error) {
321 if(Curl_pgrsUpdate(conn))
322 return CURLE_ABORTED_BY_CALLBACK;
324 if(ptr >= &data->state.buffer[BUFSIZE]) {
325 failf(data, "CONNECT response too large!");
326 return CURLE_RECV_ERROR;
329 check = Curl_timeleft(data, NULL, TRUE);
331 failf(data, "Proxy CONNECT aborted due to timeout");
332 error = SELECT_TIMEOUT; /* already too little time */
336 /* Read one byte at a time to avoid a race condition. Wait at most one
337 second before looping to ensure continuous pgrsUpdates. */
338 result = Curl_read(conn, tunnelsocket, ptr, 1, &gotbytes);
339 if(result == CURLE_AGAIN) {
340 if(SOCKET_READABLE(tunnelsocket, check<1000L?check:1000) == -1) {
341 error = SELECT_ERROR;
342 failf(data, "Proxy CONNECT aborted due to select/poll error");
351 else if(gotbytes <= 0) {
352 if(data->set.proxyauth && data->state.authproxy.avail) {
353 /* proxy auth was requested and there was proxy auth available,
354 then deem this as "mere" proxy disconnect */
355 conn->bits.proxy_connect_closed = TRUE;
356 infof(data, "Proxy CONNECT connection closed\n");
359 error = SELECT_ERROR;
360 failf(data, "Proxy CONNECT aborted");
366 /* We got a byte of data */
370 /* This means we are currently ignoring a response-body */
372 nread = 0; /* make next read start over in the read buffer */
373 ptr = data->state.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! */
384 /* chunked-encoded body, so we need to do the chunked dance
385 properly to know when the end of the body is reached */
387 ssize_t tookcareof = 0;
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, ptr, 1, &tookcareof);
392 if(r == CHUNKE_STOP) {
393 /* we're done reading chunks! */
394 infof(data, "chunk reading DONE\n");
396 /* we did the full CONNECT treatment, go COMPLETE */
397 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
403 perline++; /* amount of bytes in this line so far */
405 /* if this is not the end of a header line then continue */
411 /* convert from the network encoding */
412 result = Curl_convert_from_network(data, line_start, perline);
413 /* Curl_convert_from_network calls failf if unsuccessful */
417 /* output debug if that is requested */
418 if(data->set.verbose)
419 Curl_debug(data, CURLINFO_HEADER_IN,
420 line_start, (size_t)perline, conn);
422 /* send the header to the callback */
423 writetype = CLIENTWRITE_HEADER;
424 if(data->set.include_header)
425 writetype |= CLIENTWRITE_BODY;
427 result = Curl_client_write(conn, writetype, line_start, perline);
429 data->info.header_size += (long)perline;
430 data->req.headerbytecount += (long)perline;
435 /* Newlines are CRLF, so the CR is ignored as the line isn't
436 really terminated until the LF comes. Treat a following CR
437 as end-of-headers as well.*/
439 if(('\r' == line_start[0]) ||
440 ('\n' == line_start[0])) {
441 /* end of response-headers from the proxy */
442 nread = 0; /* make next read start over in the read
444 ptr = data->state.buffer;
445 if((407 == k->httpcode) && !data->state.authproblem) {
446 /* If we get a 407 response code with content length
447 when we have no auth problem, we must ignore the
448 whole response-body */
452 infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
453 " bytes of response-body\n", cl);
455 else if(chunked_encoding) {
458 infof(data, "Ignore chunked response-body\n");
460 /* We set ignorebody true here since the chunked
461 decoder function will acknowledge that. Pay
462 attention so that this is cleared again when this
464 k->ignorebody = TRUE;
466 if(line_start[1] == '\n') {
467 /* this can only be a LF if the letter at index 0
472 /* now parse the chunked piece of data so that we can
473 properly tell when the stream ends */
474 r = Curl_httpchunk_read(conn, line_start + 1, 1, &gotbytes);
475 if(r == CHUNKE_STOP) {
476 /* we're done reading chunks! */
477 infof(data, "chunk reading DONE\n");
479 /* we did the full CONNECT treatment, go to
481 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
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 */
493 /* we did the full CONNECT treatment, go to COMPLETE */
494 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
498 line_start[perline] = 0; /* zero terminate the buffer */
499 if((checkprefix("WWW-Authenticate:", line_start) &&
500 (401 == k->httpcode)) ||
501 (checkprefix("Proxy-authenticate:", line_start) &&
502 (407 == k->httpcode))) {
504 bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
505 char *auth = Curl_copy_header_value(line_start);
507 return CURLE_OUT_OF_MEMORY;
509 result = Curl_http_input_auth(conn, proxy, auth);
516 else if(checkprefix("Content-Length:", line_start)) {
517 if(k->httpcode/100 == 2) {
518 /* A server MUST NOT send any Transfer-Encoding or
519 Content-Length header fields in a 2xx (Successful)
520 response to CONNECT. (RFC 7231 section 4.3.6) */
521 failf(data, "Content-Length: in %03d response",
523 return CURLE_RECV_ERROR;
526 cl = curlx_strtoofft(line_start +
527 strlen("Content-Length:"), NULL, 10);
529 else if(Curl_compareheader(line_start, "Connection:", "close"))
530 closeConnection = TRUE;
531 else if(Curl_compareheader(line_start,
532 "Transfer-Encoding:",
534 if(k->httpcode/100 == 2) {
535 /* A server MUST NOT send any Transfer-Encoding or
536 Content-Length header fields in a 2xx (Successful)
537 response to CONNECT. (RFC 7231 section 4.3.6) */
538 failf(data, "Transfer-Encoding: in %03d response", k->httpcode);
539 return CURLE_RECV_ERROR;
541 infof(data, "CONNECT responded chunked\n");
542 chunked_encoding = TRUE;
543 /* init our chunky engine */
544 Curl_httpchunk_init(conn);
546 else if(Curl_compareheader(line_start, "Proxy-Connection:", "close"))
547 closeConnection = TRUE;
548 else if(2 == sscanf(line_start, "HTTP/1.%d %d",
551 /* store the HTTP code from the proxy */
552 data->info.httpproxycode = k->httpcode;
555 perline = 0; /* line starts over here */
556 ptr = data->state.buffer;
558 } /* while there's buffer left and loop is requested */
560 if(Curl_pgrsUpdate(conn))
561 return CURLE_ABORTED_BY_CALLBACK;
564 return CURLE_RECV_ERROR;
566 if(data->info.httpproxycode != 200) {
567 /* Deal with the possibly already received authenticate
568 headers. 'newurl' is set to a new URL if we must loop. */
569 result = Curl_http_auth_act(conn);
574 /* the connection has been marked for closure, most likely in the
575 Curl_http_auth_act() function and thus we can kill it at once
577 closeConnection = TRUE;
580 if(closeConnection && data->req.newurl) {
581 /* Connection closed by server. Don't use it anymore */
582 Curl_closesocket(conn, conn->sock[sockindex]);
583 conn->sock[sockindex] = CURL_SOCKET_BAD;
586 } /* END READING RESPONSE PHASE */
588 /* If we are supposed to continue and request a new URL, which basically
589 * means the HTTP authentication is still going on so if the tunnel
590 * is complete we start over in INIT state */
591 if(data->req.newurl &&
592 (TUNNEL_COMPLETE == conn->tunnel_state[sockindex])) {
593 conn->tunnel_state[sockindex] = TUNNEL_INIT;
594 infof(data, "TUNNEL_STATE switched to: %d\n",
595 conn->tunnel_state[sockindex]);
598 } while(data->req.newurl);
600 if(200 != data->req.httpcode) {
601 if(closeConnection && data->req.newurl) {
602 conn->bits.proxy_connect_closed = TRUE;
603 infof(data, "Connect me again please\n");
606 free(data->req.newurl);
607 data->req.newurl = NULL;
608 /* failure, close this connection to avoid re-use */
609 streamclose(conn, "proxy CONNECT failure");
610 Curl_closesocket(conn, conn->sock[sockindex]);
611 conn->sock[sockindex] = CURL_SOCKET_BAD;
614 /* to back to init state */
615 conn->tunnel_state[sockindex] = TUNNEL_INIT;
617 if(conn->bits.proxy_connect_closed)
618 /* this is not an error, just part of the connection negotiation */
621 failf(data, "Received HTTP code %d from proxy after CONNECT",
623 return CURLE_RECV_ERROR;
627 conn->tunnel_state[sockindex] = 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 OK to CONNECT request\n");
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
643 #endif /* CURL_DISABLE_PROXY */