1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2016, 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"
38 #include "curl_printf.h"
41 #include "curl_memory.h"
42 /* The last #include file should be: */
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;
55 /* We want "seamless" operations through HTTP proxy tunnel */
57 /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the
58 * member conn->proto.http; we want [protocol] through HTTP and we have
59 * to change the member temporarily for connecting to the HTTP
60 * proxy. After Curl_proxyCONNECT we have to set back the member to the
63 * This function might be called several times in the multi interface case
64 * if the proxy's CONNTECT response is not instant.
66 prot_save = conn->data->req.protop;
67 memset(&http_proxy, 0, sizeof(http_proxy));
68 conn->data->req.protop = &http_proxy;
69 connkeep(conn, "HTTP proxy CONNECT");
70 result = Curl_proxyCONNECT(conn, FIRSTSOCKET,
71 conn->host.name, conn->remote_port, FALSE);
72 conn->data->req.protop = prot_save;
73 if(CURLE_OK != result)
75 Curl_safefree(conn->allocptr.proxyuserpwd);
77 return CURLE_NOT_BUILT_IN;
80 /* no HTTP tunnel proxy, just return */
85 * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
86 * function will issue the necessary commands to get a seamless tunnel through
87 * this proxy. After that, the socket can be used just as a normal socket.
89 * 'blocking' set to TRUE means that this function will do the entire CONNECT
90 * + response in a blocking fashion. Should be avoided!
93 CURLcode Curl_proxyCONNECT(struct connectdata *conn,
100 struct SessionHandle *data=conn->data;
101 struct SingleRequest *k = &data->req;
103 curl_socket_t tunnelsocket = conn->sock[sockindex];
105 bool closeConnection = FALSE;
106 bool chunked_encoding = FALSE;
110 #define SELECT_ERROR 1
111 #define SELECT_TIMEOUT 2
112 int error = SELECT_OK;
114 if(conn->tunnel_state[sockindex] == TUNNEL_COMPLETE)
115 return CURLE_OK; /* CONNECT is already completed */
117 conn->bits.proxy_connect_closed = FALSE;
120 if(TUNNEL_INIT == conn->tunnel_state[sockindex]) {
121 /* BEGIN CONNECT PHASE */
123 Curl_send_buffer *req_buffer;
125 infof(data, "Establish HTTP proxy tunnel to %s:%hu\n",
126 hostname, remote_port);
128 /* This only happens if we've looped here due to authentication
129 reasons, and we don't really use the newly cloned URL here
130 then. Just free() it. */
131 free(data->req.newurl);
132 data->req.newurl = NULL;
134 /* initialize a dynamic send-buffer */
135 req_buffer = Curl_add_buffer_init();
138 return CURLE_OUT_OF_MEMORY;
140 host_port = aprintf("%s:%hu", hostname, remote_port);
142 Curl_add_buffer_free(req_buffer);
143 return CURLE_OUT_OF_MEMORY;
146 /* Setup the proxy-authorization header, if any */
147 result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
152 char *host=(char *)"";
153 const char *useragent="";
154 const char *http = (conn->proxytype == CURLPROXY_HTTP_1_0) ?
156 char *hostheader= /* host:port with IPv6 support */
157 aprintf("%s%s%s:%hu", conn->bits.ipv6_ip?"[":"",
158 hostname, conn->bits.ipv6_ip?"]":"",
161 Curl_add_buffer_free(req_buffer);
162 return CURLE_OUT_OF_MEMORY;
165 if(!Curl_checkProxyheaders(conn, "Host:")) {
166 host = aprintf("Host: %s\r\n", hostheader);
169 Curl_add_buffer_free(req_buffer);
170 return CURLE_OUT_OF_MEMORY;
173 if(!Curl_checkProxyheaders(conn, "User-Agent:") &&
174 data->set.str[STRING_USERAGENT])
175 useragent = conn->allocptr.uagent;
178 Curl_add_bufferf(req_buffer,
179 "CONNECT %s HTTP/%s\r\n"
181 "%s" /* Proxy-Authorization */
182 "%s", /* User-Agent */
186 conn->allocptr.proxyuserpwd?
187 conn->allocptr.proxyuserpwd:"",
195 result = Curl_add_custom_headers(conn, TRUE, req_buffer);
198 /* CRLF terminate the request */
199 result = Curl_add_bufferf(req_buffer, "\r\n");
202 /* Send the connect request to the proxy */
205 Curl_add_buffer_send(req_buffer, conn,
206 &data->info.request_size, 0, sockindex);
210 failf(data, "Failed sending CONNECT to proxy");
213 Curl_add_buffer_free(req_buffer);
217 conn->tunnel_state[sockindex] = TUNNEL_CONNECT;
218 } /* END CONNECT PHASE */
220 check = Curl_timeleft(data, NULL, TRUE);
222 failf(data, "Proxy CONNECT aborted due to timeout");
223 return CURLE_RECV_ERROR;
227 if(0 == Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD, 0))
228 /* return so we'll be called again polling-style */
232 "Read response immediately from proxy CONNECT\n"));
236 /* at this point, the tunnel_connecting phase is over. */
238 { /* READING RESPONSE PHASE */
239 size_t nread; /* total size read */
240 int perline; /* count bytes per line */
246 ptr=data->state.buffer;
252 while((nread<BUFSIZE) && (keepon && !error)) {
254 check = Curl_timeleft(data, NULL, TRUE);
256 failf(data, "Proxy CONNECT aborted due to timeout");
257 error = SELECT_TIMEOUT; /* already too little time */
261 /* loop every second at least, less if the timeout is near */
262 switch (Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD,
263 check<1000L?check:1000)) {
264 case -1: /* select() error, stop reading */
265 error = SELECT_ERROR;
266 failf(data, "Proxy CONNECT aborted due to select/poll error");
268 case 0: /* timeout */
271 DEBUGASSERT(ptr+BUFSIZE-nread <= data->state.buffer+BUFSIZE+1);
272 result = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread,
274 if(result==CURLE_AGAIN)
275 continue; /* go loop yourself */
278 else if(gotbytes <= 0) {
280 if(data->set.proxyauth && data->state.authproxy.avail) {
281 /* proxy auth was requested and there was proxy auth available,
282 then deem this as "mere" proxy disconnect */
283 conn->bits.proxy_connect_closed = TRUE;
284 infof(data, "Proxy CONNECT connection closed\n");
287 error = SELECT_ERROR;
288 failf(data, "Proxy CONNECT aborted");
293 * We got a whole chunk of data, which can be anything from one
294 * byte to a set of lines and possibly just a piece of the last
302 /* This means we are currently ignoring a response-body */
304 nread = 0; /* make next read start over in the read buffer */
305 ptr=data->state.buffer;
307 /* A Content-Length based body: simply count down the counter
308 and make sure to break out of the loop when we're done! */
316 /* chunked-encoded body, so we need to do the chunked dance
317 properly to know when the end of the body is reached */
319 ssize_t tookcareof=0;
321 /* now parse the chunked piece of data so that we can
322 properly tell when the stream ends */
323 r = Curl_httpchunk_read(conn, ptr, gotbytes, &tookcareof);
324 if(r == CHUNKE_STOP) {
325 /* we're done reading chunks! */
326 infof(data, "chunk reading DONE\n");
328 /* we did the full CONNECT treatment, go COMPLETE */
329 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
332 infof(data, "Read %zd bytes of chunk, continue\n",
337 for(i = 0; i < gotbytes; ptr++, i++) {
338 perline++; /* amount of bytes in this line so far */
343 /* convert from the network encoding */
344 result = Curl_convert_from_network(data, line_start,
346 /* Curl_convert_from_network calls failf if unsuccessful */
350 /* output debug if that is requested */
351 if(data->set.verbose)
352 Curl_debug(data, CURLINFO_HEADER_IN,
353 line_start, (size_t)perline, conn);
355 /* send the header to the callback */
356 writetype = CLIENTWRITE_HEADER;
357 if(data->set.include_header)
358 writetype |= CLIENTWRITE_BODY;
360 result = Curl_client_write(conn, writetype, line_start,
363 data->info.header_size += (long)perline;
364 data->req.headerbytecount += (long)perline;
369 /* Newlines are CRLF, so the CR is ignored as the line isn't
370 really terminated until the LF comes. Treat a following CR
371 as end-of-headers as well.*/
373 if(('\r' == line_start[0]) ||
374 ('\n' == line_start[0])) {
375 /* end of response-headers from the proxy */
376 nread = 0; /* make next read start over in the read
378 ptr=data->state.buffer;
379 if((407 == k->httpcode) && !data->state.authproblem) {
380 /* If we get a 407 response code with content length
381 when we have no auth problem, we must ignore the
382 whole response-body */
386 infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
387 " bytes of response-body\n", cl);
389 /* remove the remaining chunk of what we already
391 cl -= (gotbytes - i);
394 /* if the whole thing was already read, we are done!
398 else if(chunked_encoding) {
400 /* We set ignorebody true here since the chunked
401 decoder function will acknowledge that. Pay
402 attention so that this is cleared again when this
404 k->ignorebody = TRUE;
405 infof(data, "%zd bytes of chunk left\n", gotbytes-i);
407 if(line_start[1] == '\n') {
408 /* this can only be a LF if the letter at index 0
414 /* now parse the chunked piece of data so that we can
415 properly tell when the stream ends */
416 r = Curl_httpchunk_read(conn, line_start+1,
417 gotbytes -i, &gotbytes);
418 if(r == CHUNKE_STOP) {
419 /* we're done reading chunks! */
420 infof(data, "chunk reading DONE\n");
422 /* we did the full CONNECT treatment, go to
424 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
427 infof(data, "Read %zd bytes of chunk, continue\n",
431 /* without content-length or chunked encoding, we
432 can't keep the connection alive since the close is
433 the end signal so we bail out at once instead */
439 if(200 == data->info.httpproxycode) {
441 failf(data, "Proxy CONNECT followed by %zd bytes "
442 "of opaque data. Data ignored (known bug #39)",
446 /* we did the full CONNECT treatment, go to COMPLETE */
447 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
448 break; /* breaks out of for-loop, not switch() */
451 /* keep a backup of the position we are about to blank */
452 letter = line_start[perline];
453 line_start[perline]=0; /* zero terminate the buffer */
454 if((checkprefix("WWW-Authenticate:", line_start) &&
455 (401 == k->httpcode)) ||
456 (checkprefix("Proxy-authenticate:", line_start) &&
457 (407 == k->httpcode))) {
459 bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
460 char *auth = Curl_copy_header_value(line_start);
462 return CURLE_OUT_OF_MEMORY;
464 result = Curl_http_input_auth(conn, proxy, auth);
471 else if(checkprefix("Content-Length:", line_start)) {
472 cl = curlx_strtoofft(line_start +
473 strlen("Content-Length:"), NULL, 10);
475 else if(Curl_compareheader(line_start,
476 "Connection:", "close"))
477 closeConnection = TRUE;
478 else if(Curl_compareheader(line_start,
479 "Transfer-Encoding:",
481 infof(data, "CONNECT responded chunked\n");
482 chunked_encoding = TRUE;
483 /* init our chunky engine */
484 Curl_httpchunk_init(conn);
486 else if(Curl_compareheader(line_start,
487 "Proxy-Connection:", "close"))
488 closeConnection = TRUE;
489 else if(2 == sscanf(line_start, "HTTP/1.%d %d",
492 /* store the HTTP code from the proxy */
493 data->info.httpproxycode = k->httpcode;
495 /* put back the letter we blanked out before */
496 line_start[perline]= letter;
498 perline=0; /* line starts over here */
499 line_start = ptr+1; /* this skips the zero byte we wrote */
505 if(Curl_pgrsUpdate(conn))
506 return CURLE_ABORTED_BY_CALLBACK;
507 } /* while there's buffer left and loop is requested */
510 return CURLE_RECV_ERROR;
512 if(data->info.httpproxycode != 200) {
513 /* Deal with the possibly already received authenticate
514 headers. 'newurl' is set to a new URL if we must loop. */
515 result = Curl_http_auth_act(conn);
520 /* the connection has been marked for closure, most likely in the
521 Curl_http_auth_act() function and thus we can kill it at once
524 closeConnection = TRUE;
527 if(closeConnection && data->req.newurl) {
528 /* Connection closed by server. Don't use it anymore */
529 Curl_closesocket(conn, conn->sock[sockindex]);
530 conn->sock[sockindex] = CURL_SOCKET_BAD;
533 } /* END READING RESPONSE PHASE */
535 /* If we are supposed to continue and request a new URL, which basically
536 * means the HTTP authentication is still going on so if the tunnel
537 * is complete we start over in INIT state */
538 if(data->req.newurl &&
539 (TUNNEL_COMPLETE == conn->tunnel_state[sockindex])) {
540 conn->tunnel_state[sockindex] = TUNNEL_INIT;
541 infof(data, "TUNNEL_STATE switched to: %d\n",
542 conn->tunnel_state[sockindex]);
545 } while(data->req.newurl);
547 if(200 != data->req.httpcode) {
548 if(closeConnection && data->req.newurl) {
549 conn->bits.proxy_connect_closed = TRUE;
550 infof(data, "Connect me again please\n");
553 free(data->req.newurl);
554 data->req.newurl = NULL;
555 /* failure, close this connection to avoid re-use */
556 connclose(conn, "proxy CONNECT failure");
557 Curl_closesocket(conn, conn->sock[sockindex]);
558 conn->sock[sockindex] = CURL_SOCKET_BAD;
561 /* to back to init state */
562 conn->tunnel_state[sockindex] = TUNNEL_INIT;
564 if(conn->bits.proxy_connect_closed)
565 /* this is not an error, just part of the connection negotiation */
568 failf(data, "Received HTTP code %d from proxy after CONNECT",
570 return CURLE_RECV_ERROR;
574 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
576 /* If a proxy-authorization header was used for the proxy, then we should
577 make sure that it isn't accidentally used for the document request
578 after we've connected. So let's free and clear it here. */
579 Curl_safefree(conn->allocptr.proxyuserpwd);
580 conn->allocptr.proxyuserpwd = NULL;
582 data->state.authproxy.done = TRUE;
584 infof (data, "Proxy replied OK to CONNECT request\n");
585 data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
586 conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the
590 #endif /* CURL_DISABLE_PROXY */