1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2015, 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"
36 #include "non-ascii.h"
40 /* The last 3 #include files should be in this order */
41 #include "curl_printf.h"
42 #include "curl_memory.h"
45 CURLcode Curl_proxy_connect(struct connectdata *conn)
47 if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
48 #ifndef CURL_DISABLE_PROXY
49 /* for [protocol] tunneled through HTTP proxy */
50 struct HTTP http_proxy;
57 /* We want "seamless" operations through HTTP proxy tunnel */
59 /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the
60 * member conn->proto.http; we want [protocol] through HTTP and we have
61 * to change the member temporarily for connecting to the HTTP
62 * proxy. After Curl_proxyCONNECT we have to set back the member to the
65 * This function might be called several times in the multi interface case
66 * if the proxy's CONNTECT response is not instant.
68 prot_save = conn->data->req.protop;
69 memset(&http_proxy, 0, sizeof(http_proxy));
70 conn->data->req.protop = &http_proxy;
71 connkeep(conn, "HTTP proxy CONNECT");
72 if(conn->bits.conn_to_host)
73 hostname = conn->conn_to_host.name;
75 hostname = conn->host.name;
76 if(conn->bits.conn_to_port)
77 remote_port = conn->conn_to_port;
79 remote_port = conn->remote_port;
80 result = Curl_proxyCONNECT(conn, FIRSTSOCKET, hostname,
82 conn->data->req.protop = prot_save;
83 if(CURLE_OK != result)
85 Curl_safefree(conn->allocptr.proxyuserpwd);
87 return CURLE_NOT_BUILT_IN;
90 /* no HTTP tunnel proxy, just return */
95 * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
96 * function will issue the necessary commands to get a seamless tunnel through
97 * this proxy. After that, the socket can be used just as a normal socket.
99 * 'blocking' set to TRUE means that this function will do the entire CONNECT
100 * + response in a blocking fashion. Should be avoided!
103 CURLcode Curl_proxyCONNECT(struct connectdata *conn,
105 const char *hostname,
110 struct Curl_easy *data=conn->data;
111 struct SingleRequest *k = &data->req;
113 curl_socket_t tunnelsocket = conn->sock[sockindex];
115 bool closeConnection = FALSE;
116 bool chunked_encoding = FALSE;
120 #define SELECT_ERROR 1
121 #define SELECT_TIMEOUT 2
122 int error = SELECT_OK;
124 if(conn->tunnel_state[sockindex] == TUNNEL_COMPLETE)
125 return CURLE_OK; /* CONNECT is already completed */
127 conn->bits.proxy_connect_closed = FALSE;
130 if(TUNNEL_INIT == conn->tunnel_state[sockindex]) {
131 /* BEGIN CONNECT PHASE */
133 Curl_send_buffer *req_buffer;
135 infof(data, "Establish HTTP proxy tunnel to %s:%hu\n",
136 hostname, remote_port);
138 /* This only happens if we've looped here due to authentication
139 reasons, and we don't really use the newly cloned URL here
140 then. Just free() it. */
141 free(data->req.newurl);
142 data->req.newurl = NULL;
144 /* initialize a dynamic send-buffer */
145 req_buffer = Curl_add_buffer_init();
148 return CURLE_OUT_OF_MEMORY;
150 host_port = aprintf("%s:%hu", hostname, remote_port);
152 Curl_add_buffer_free(req_buffer);
153 return CURLE_OUT_OF_MEMORY;
156 /* Setup the proxy-authorization header, if any */
157 result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
162 char *host=(char *)"";
163 const char *proxyconn="";
164 const char *useragent="";
165 const char *http = (conn->proxytype == CURLPROXY_HTTP_1_0) ?
167 bool ipv6_ip = conn->bits.ipv6_ip;
170 /* the hostname may be different */
171 if(hostname != conn->host.name)
172 ipv6_ip = (strchr(hostname, ':') != NULL);
173 hostheader= /* host:port with IPv6 support */
174 aprintf("%s%s%s:%hu", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
177 Curl_add_buffer_free(req_buffer);
178 return CURLE_OUT_OF_MEMORY;
181 if(!Curl_checkProxyheaders(conn, "Host:")) {
182 host = aprintf("Host: %s\r\n", hostheader);
185 Curl_add_buffer_free(req_buffer);
186 return CURLE_OUT_OF_MEMORY;
189 if(!Curl_checkProxyheaders(conn, "Proxy-Connection:"))
190 proxyconn = "Proxy-Connection: Keep-Alive\r\n";
192 if(!Curl_checkProxyheaders(conn, "User-Agent:") &&
193 data->set.str[STRING_USERAGENT])
194 useragent = conn->allocptr.uagent;
197 Curl_add_bufferf(req_buffer,
198 "CONNECT %s HTTP/%s\r\n"
200 "%s" /* Proxy-Authorization */
201 "%s" /* User-Agent */
202 "%s", /* Proxy-Connection */
206 conn->allocptr.proxyuserpwd?
207 conn->allocptr.proxyuserpwd:"",
216 result = Curl_add_custom_headers(conn, TRUE, req_buffer);
219 /* CRLF terminate the request */
220 result = Curl_add_bufferf(req_buffer, "\r\n");
223 /* Send the connect request to the proxy */
226 Curl_add_buffer_send(req_buffer, conn,
227 &data->info.request_size, 0, sockindex);
231 failf(data, "Failed sending CONNECT to proxy");
234 Curl_add_buffer_free(req_buffer);
238 conn->tunnel_state[sockindex] = TUNNEL_CONNECT;
239 } /* END CONNECT PHASE */
241 check = Curl_timeleft(data, NULL, TRUE);
243 failf(data, "Proxy CONNECT aborted due to timeout");
244 return CURLE_RECV_ERROR;
248 if(0 == Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD, 0))
249 /* return so we'll be called again polling-style */
253 "Read response immediately from proxy CONNECT\n"));
257 /* at this point, the tunnel_connecting phase is over. */
259 { /* READING RESPONSE PHASE */
260 size_t nread; /* total size read */
261 int perline; /* count bytes per line */
267 ptr=data->state.buffer;
273 while((nread<BUFSIZE) && (keepon && !error)) {
275 check = Curl_timeleft(data, NULL, TRUE);
277 failf(data, "Proxy CONNECT aborted due to timeout");
278 error = SELECT_TIMEOUT; /* already too little time */
282 /* loop every second at least, less if the timeout is near */
283 switch (Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD,
284 check<1000L?check:1000)) {
285 case -1: /* select() error, stop reading */
286 error = SELECT_ERROR;
287 failf(data, "Proxy CONNECT aborted due to select/poll error");
289 case 0: /* timeout */
292 DEBUGASSERT(ptr+BUFSIZE-nread <= data->state.buffer+BUFSIZE+1);
293 result = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread,
295 if(result==CURLE_AGAIN)
296 continue; /* go loop yourself */
299 else if(gotbytes <= 0) {
301 if(data->set.proxyauth && data->state.authproxy.avail) {
302 /* proxy auth was requested and there was proxy auth available,
303 then deem this as "mere" proxy disconnect */
304 conn->bits.proxy_connect_closed = TRUE;
305 infof(data, "Proxy CONNECT connection closed\n");
308 error = SELECT_ERROR;
309 failf(data, "Proxy CONNECT aborted");
314 * We got a whole chunk of data, which can be anything from one
315 * byte to a set of lines and possibly just a piece of the last
323 /* This means we are currently ignoring a response-body */
325 nread = 0; /* make next read start over in the read buffer */
326 ptr=data->state.buffer;
328 /* A Content-Length based body: simply count down the counter
329 and make sure to break out of the loop when we're done! */
337 /* chunked-encoded body, so we need to do the chunked dance
338 properly to know when the end of the body is reached */
340 ssize_t tookcareof=0;
342 /* now parse the chunked piece of data so that we can
343 properly tell when the stream ends */
344 r = Curl_httpchunk_read(conn, ptr, gotbytes, &tookcareof);
345 if(r == CHUNKE_STOP) {
346 /* we're done reading chunks! */
347 infof(data, "chunk reading DONE\n");
349 /* we did the full CONNECT treatment, go COMPLETE */
350 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
353 infof(data, "Read %zd bytes of chunk, continue\n",
358 for(i = 0; i < gotbytes; ptr++, i++) {
359 perline++; /* amount of bytes in this line so far */
364 /* convert from the network encoding */
365 result = Curl_convert_from_network(data, line_start,
367 /* Curl_convert_from_network calls failf if unsuccessful */
371 /* output debug if that is requested */
372 if(data->set.verbose)
373 Curl_debug(data, CURLINFO_HEADER_IN,
374 line_start, (size_t)perline, conn);
376 /* send the header to the callback */
377 writetype = CLIENTWRITE_HEADER;
378 if(data->set.include_header)
379 writetype |= CLIENTWRITE_BODY;
381 result = Curl_client_write(conn, writetype, line_start,
384 data->info.header_size += (long)perline;
385 data->req.headerbytecount += (long)perline;
390 /* Newlines are CRLF, so the CR is ignored as the line isn't
391 really terminated until the LF comes. Treat a following CR
392 as end-of-headers as well.*/
394 if(('\r' == line_start[0]) ||
395 ('\n' == line_start[0])) {
396 /* end of response-headers from the proxy */
397 nread = 0; /* make next read start over in the read
399 ptr=data->state.buffer;
400 if((407 == k->httpcode) && !data->state.authproblem) {
401 /* If we get a 407 response code with content length
402 when we have no auth problem, we must ignore the
403 whole response-body */
407 infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
408 " bytes of response-body\n", cl);
410 /* remove the remaining chunk of what we already
412 cl -= (gotbytes - i);
415 /* if the whole thing was already read, we are done!
419 else if(chunked_encoding) {
421 /* We set ignorebody true here since the chunked
422 decoder function will acknowledge that. Pay
423 attention so that this is cleared again when this
425 k->ignorebody = TRUE;
426 infof(data, "%zd bytes of chunk left\n", gotbytes-i);
428 if(line_start[1] == '\n') {
429 /* this can only be a LF if the letter at index 0
435 /* now parse the chunked piece of data so that we can
436 properly tell when the stream ends */
437 r = Curl_httpchunk_read(conn, line_start+1,
438 gotbytes -i, &gotbytes);
439 if(r == CHUNKE_STOP) {
440 /* we're done reading chunks! */
441 infof(data, "chunk reading DONE\n");
443 /* we did the full CONNECT treatment, go to
445 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
448 infof(data, "Read %zd bytes of chunk, continue\n",
452 /* without content-length or chunked encoding, we
453 can't keep the connection alive since the close is
454 the end signal so we bail out at once instead */
460 if(200 == data->info.httpproxycode) {
462 failf(data, "Proxy CONNECT followed by %zd bytes "
463 "of opaque data. Data ignored (known bug #39)",
467 /* we did the full CONNECT treatment, go to COMPLETE */
468 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
469 break; /* breaks out of for-loop, not switch() */
472 /* keep a backup of the position we are about to blank */
473 letter = line_start[perline];
474 line_start[perline]=0; /* zero terminate the buffer */
475 if((checkprefix("WWW-Authenticate:", line_start) &&
476 (401 == k->httpcode)) ||
477 (checkprefix("Proxy-authenticate:", line_start) &&
478 (407 == k->httpcode))) {
480 bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
481 char *auth = Curl_copy_header_value(line_start);
483 return CURLE_OUT_OF_MEMORY;
485 result = Curl_http_input_auth(conn, proxy, auth);
492 else if(checkprefix("Content-Length:", line_start)) {
493 cl = curlx_strtoofft(line_start +
494 strlen("Content-Length:"), NULL, 10);
496 else if(Curl_compareheader(line_start,
497 "Connection:", "close"))
498 closeConnection = TRUE;
499 else if(Curl_compareheader(line_start,
500 "Transfer-Encoding:",
502 infof(data, "CONNECT responded chunked\n");
503 chunked_encoding = TRUE;
504 /* init our chunky engine */
505 Curl_httpchunk_init(conn);
507 else if(Curl_compareheader(line_start,
508 "Proxy-Connection:", "close"))
509 closeConnection = TRUE;
510 else if(2 == sscanf(line_start, "HTTP/1.%d %d",
513 /* store the HTTP code from the proxy */
514 data->info.httpproxycode = k->httpcode;
516 /* put back the letter we blanked out before */
517 line_start[perline]= letter;
519 perline=0; /* line starts over here */
520 line_start = ptr+1; /* this skips the zero byte we wrote */
526 if(Curl_pgrsUpdate(conn))
527 return CURLE_ABORTED_BY_CALLBACK;
528 } /* while there's buffer left and loop is requested */
531 return CURLE_RECV_ERROR;
533 if(data->info.httpproxycode != 200) {
534 /* Deal with the possibly already received authenticate
535 headers. 'newurl' is set to a new URL if we must loop. */
536 result = Curl_http_auth_act(conn);
541 /* the connection has been marked for closure, most likely in the
542 Curl_http_auth_act() function and thus we can kill it at once
545 closeConnection = TRUE;
548 if(closeConnection && data->req.newurl) {
549 /* Connection closed by server. Don't use it anymore */
550 Curl_closesocket(conn, conn->sock[sockindex]);
551 conn->sock[sockindex] = CURL_SOCKET_BAD;
554 } /* END READING RESPONSE PHASE */
556 /* If we are supposed to continue and request a new URL, which basically
557 * means the HTTP authentication is still going on so if the tunnel
558 * is complete we start over in INIT state */
559 if(data->req.newurl &&
560 (TUNNEL_COMPLETE == conn->tunnel_state[sockindex])) {
561 conn->tunnel_state[sockindex] = TUNNEL_INIT;
562 infof(data, "TUNNEL_STATE switched to: %d\n",
563 conn->tunnel_state[sockindex]);
566 } while(data->req.newurl);
568 if(200 != data->req.httpcode) {
569 if(closeConnection && data->req.newurl) {
570 conn->bits.proxy_connect_closed = TRUE;
571 infof(data, "Connect me again please\n");
574 free(data->req.newurl);
575 data->req.newurl = NULL;
576 /* failure, close this connection to avoid re-use */
577 streamclose(conn, "proxy CONNECT failure");
578 Curl_closesocket(conn, conn->sock[sockindex]);
579 conn->sock[sockindex] = CURL_SOCKET_BAD;
582 /* to back to init state */
583 conn->tunnel_state[sockindex] = TUNNEL_INIT;
585 if(conn->bits.proxy_connect_closed)
586 /* this is not an error, just part of the connection negotiation */
589 failf(data, "Received HTTP code %d from proxy after CONNECT",
591 return CURLE_RECV_ERROR;
595 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
597 /* If a proxy-authorization header was used for the proxy, then we should
598 make sure that it isn't accidentally used for the document request
599 after we've connected. So let's free and clear it here. */
600 Curl_safefree(conn->allocptr.proxyuserpwd);
601 conn->allocptr.proxyuserpwd = NULL;
603 data->state.authproxy.done = TRUE;
605 infof (data, "Proxy replied OK to CONNECT request\n");
606 data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
607 conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the
611 #endif /* CURL_DISABLE_PROXY */