1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2013, 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 http://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)
27 #include "curl_urldata.h"
28 #include <curl/curl.h>
29 #include "curl_http_proxy.h"
30 #include "curl_sendf.h"
31 #include "curl_http.h"
33 #include "curl_select.h"
34 #include "curl_rawstr.h"
35 #include "curl_progress.h"
36 #include "curl_non_ascii.h"
37 #include "curl_connect.h"
39 #define _MPRINTF_REPLACE /* use our functions only */
40 #include <curl/mprintf.h>
44 #include "curl_memory.h"
45 /* The last #include file should be: */
46 #include "curl_memdebug.h"
48 CURLcode Curl_proxy_connect(struct connectdata *conn)
50 if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
51 #ifndef CURL_DISABLE_PROXY
52 /* for [protocol] tunneled through HTTP proxy */
53 struct HTTP http_proxy;
58 /* We want "seamless" operations through HTTP proxy tunnel */
60 /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the
61 * member conn->proto.http; we want [protocol] through HTTP and we have
62 * to change the member temporarily for connecting to the HTTP
63 * proxy. After Curl_proxyCONNECT we have to set back the member to the
66 * This function might be called several times in the multi interface case
67 * if the proxy's CONNTECT response is not instant.
69 prot_save = conn->data->state.proto.generic;
70 memset(&http_proxy, 0, sizeof(http_proxy));
71 conn->data->state.proto.http = &http_proxy;
72 conn->bits.close = FALSE;
73 result = Curl_proxyCONNECT(conn, FIRSTSOCKET,
74 conn->host.name, conn->remote_port);
75 conn->data->state.proto.generic = prot_save;
76 if(CURLE_OK != result)
79 return CURLE_NOT_BUILT_IN;
82 /* no HTTP tunnel proxy, just return */
87 * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
88 * function will issue the necessary commands to get a seamless tunnel through
89 * this proxy. After that, the socket can be used just as a normal socket.
91 * This badly needs to be rewritten. CONNECT should be sent and dealt with
92 * like any ordinary HTTP request, and not specially crafted like this. This
93 * function only remains here like this for now since the rewrite is a bit too
94 * much work to do at the moment.
96 * This function is BLOCKING which is nasty for all multi interface using apps.
99 CURLcode Curl_proxyCONNECT(struct connectdata *conn,
101 const char *hostname,
102 unsigned short remote_port)
105 struct SessionHandle *data=conn->data;
106 struct SingleRequest *k = &data->req;
109 data->set.timeout?data->set.timeout:PROXY_TIMEOUT; /* in milliseconds */
110 curl_socket_t tunnelsocket = conn->sock[sockindex];
112 bool closeConnection = FALSE;
113 bool chunked_encoding = FALSE;
117 #define SELECT_ERROR 1
118 #define SELECT_TIMEOUT 2
119 int error = SELECT_OK;
121 if(conn->tunnel_state[sockindex] == TUNNEL_COMPLETE)
122 return CURLE_OK; /* CONNECT is already completed */
124 conn->bits.proxy_connect_closed = FALSE;
127 if(TUNNEL_INIT == conn->tunnel_state[sockindex]) {
128 /* BEGIN CONNECT PHASE */
130 Curl_send_buffer *req_buffer;
132 infof(data, "Establish HTTP proxy tunnel to %s:%hu\n",
133 hostname, remote_port);
135 if(data->req.newurl) {
136 /* This only happens if we've looped here due to authentication
137 reasons, and we don't really use the newly cloned URL here
138 then. Just free() it. */
139 free(data->req.newurl);
140 data->req.newurl = NULL;
143 /* initialize a dynamic send-buffer */
144 req_buffer = Curl_add_buffer_init();
147 return CURLE_OUT_OF_MEMORY;
149 host_port = aprintf("%s:%hu", hostname, remote_port);
152 return CURLE_OUT_OF_MEMORY;
155 /* Setup the proxy-authorization header, if any */
156 result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
160 if(CURLE_OK == result) {
161 char *host=(char *)"";
162 const char *proxyconn="";
163 const char *useragent="";
164 const char *http = (conn->proxytype == CURLPROXY_HTTP_1_0) ?
166 char *hostheader= /* host:port with IPv6 support */
167 aprintf("%s%s%s:%hu", conn->bits.ipv6_ip?"[":"",
168 hostname, conn->bits.ipv6_ip?"]":"",
172 return CURLE_OUT_OF_MEMORY;
175 if(!Curl_checkheaders(data, "Host:")) {
176 host = aprintf("Host: %s\r\n", hostheader);
180 return CURLE_OUT_OF_MEMORY;
183 if(!Curl_checkheaders(data, "Proxy-Connection:"))
184 proxyconn = "Proxy-Connection: Keep-Alive\r\n";
186 if(!Curl_checkheaders(data, "User-Agent:") &&
187 data->set.str[STRING_USERAGENT])
188 useragent = conn->allocptr.uagent;
191 Curl_add_bufferf(req_buffer,
192 "CONNECT %s HTTP/%s\r\n"
194 "%s" /* Proxy-Authorization */
195 "%s" /* User-Agent */
196 "%s", /* Proxy-Connection */
200 conn->allocptr.proxyuserpwd?
201 conn->allocptr.proxyuserpwd:"",
209 if(CURLE_OK == result)
210 result = Curl_add_custom_headers(conn, req_buffer);
212 if(CURLE_OK == result)
213 /* CRLF terminate the request */
214 result = Curl_add_bufferf(req_buffer, "\r\n");
216 if(CURLE_OK == result) {
217 /* Send the connect request to the proxy */
220 Curl_add_buffer_send(req_buffer, conn,
221 &data->info.request_size, 0, sockindex);
225 failf(data, "Failed sending CONNECT to proxy");
228 Curl_safefree(req_buffer);
232 conn->tunnel_state[sockindex] = TUNNEL_CONNECT;
233 } /* END CONNECT PHASE */
235 /* now we've issued the CONNECT and we're waiting to hear back -
236 we try not to block here in multi-mode because that might be a LONG
237 wait if the proxy cannot connect-through to the remote host. */
239 /* if timeout is requested, find out how much remaining time we have */
240 check = timeout - /* timeout time */
241 Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */
243 failf(data, "Proxy CONNECT aborted due to timeout");
244 return CURLE_RECV_ERROR;
247 /* if we're in multi-mode and we would block, return instead for a retry */
248 if(Curl_if_multi == data->state.used_interface) {
249 if(0 == Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD, 0))
250 /* return so we'll be called again polling-style */
254 "Multi mode finished polling for response from "
259 DEBUGF(infof(data, "Easy mode waiting response from proxy CONNECT\n"));
262 /* at this point, either:
263 1) we're in easy-mode and so it's okay to block waiting for a CONNECT
265 2) we're in multi-mode and we didn't block - it's either an error or we
266 now have some data waiting.
267 In any case, the tunnel_connecting phase is over. */
269 { /* BEGIN NEGOTIATION PHASE */
270 size_t nread; /* total size read */
271 int perline; /* count bytes per line */
277 ptr=data->state.buffer;
284 while((nread<BUFSIZE) && (keepon && !error)) {
286 /* if timeout is requested, find out how much remaining time we have */
287 check = timeout - /* timeout time */
288 Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */
290 failf(data, "Proxy CONNECT aborted due to timeout");
291 error = SELECT_TIMEOUT; /* already too little time */
295 /* loop every second at least, less if the timeout is near */
296 switch (Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD,
297 check<1000L?check:1000)) {
298 case -1: /* select() error, stop reading */
299 error = SELECT_ERROR;
300 failf(data, "Proxy CONNECT aborted due to select/poll error");
302 case 0: /* timeout */
305 DEBUGASSERT(ptr+BUFSIZE-nread <= data->state.buffer+BUFSIZE+1);
306 result = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread,
308 if(result==CURLE_AGAIN)
309 continue; /* go loop yourself */
312 else if(gotbytes <= 0) {
314 if(data->set.proxyauth && data->state.authproxy.avail) {
315 /* proxy auth was requested and there was proxy auth available,
316 then deem this as "mere" proxy disconnect */
317 conn->bits.proxy_connect_closed = TRUE;
320 error = SELECT_ERROR;
321 failf(data, "Proxy CONNECT aborted");
326 * We got a whole chunk of data, which can be anything from one
327 * byte to a set of lines and possibly just a piece of the last
335 /* This means we are currently ignoring a response-body */
337 nread = 0; /* make next read start over in the read buffer */
338 ptr=data->state.buffer;
340 /* A Content-Length based body: simply count down the counter
341 and make sure to break out of the loop when we're done! */
349 /* chunked-encoded body, so we need to do the chunked dance
350 properly to know when the end of the body is reached */
352 ssize_t tookcareof=0;
354 /* now parse the chunked piece of data so that we can
355 properly tell when the stream ends */
356 r = Curl_httpchunk_read(conn, ptr, gotbytes, &tookcareof);
357 if(r == CHUNKE_STOP) {
358 /* we're done reading chunks! */
359 infof(data, "chunk reading DONE\n");
361 /* we did the full CONNECT treatment, go COMPLETE */
362 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
365 infof(data, "Read %zd bytes of chunk, continue\n",
370 for(i = 0; i < gotbytes; ptr++, i++) {
371 perline++; /* amount of bytes in this line so far */
376 /* convert from the network encoding */
377 result = Curl_convert_from_network(data, line_start,
379 /* Curl_convert_from_network calls failf if unsuccessful */
383 /* output debug if that is requested */
384 if(data->set.verbose)
385 Curl_debug(data, CURLINFO_HEADER_IN,
386 line_start, (size_t)perline, conn);
388 /* send the header to the callback */
389 writetype = CLIENTWRITE_HEADER;
390 if(data->set.include_header)
391 writetype |= CLIENTWRITE_BODY;
393 result = Curl_client_write(conn, writetype, line_start,
398 /* Newlines are CRLF, so the CR is ignored as the line isn't
399 really terminated until the LF comes. Treat a following CR
400 as end-of-headers as well.*/
402 if(('\r' == line_start[0]) ||
403 ('\n' == line_start[0])) {
404 /* end of response-headers from the proxy */
405 nread = 0; /* make next read start over in the read
407 ptr=data->state.buffer;
408 if((407 == k->httpcode) && !data->state.authproblem) {
409 /* If we get a 407 response code with content length
410 when we have no auth problem, we must ignore the
411 whole response-body */
416 infof(data, "Ignore %" FORMAT_OFF_T
417 " bytes of response-body\n", cl);
418 /* remove the remaining chunk of what we already
420 cl -= (gotbytes - i);
423 /* if the whole thing was already read, we are done!
427 else if(chunked_encoding) {
429 /* We set ignorebody true here since the chunked
430 decoder function will acknowledge that. Pay
431 attention so that this is cleared again when this
433 k->ignorebody = TRUE;
434 infof(data, "%zd bytes of chunk left\n", gotbytes-i);
436 if(line_start[1] == '\n') {
437 /* this can only be a LF if the letter at index 0
443 /* now parse the chunked piece of data so that we can
444 properly tell when the stream ends */
445 r = Curl_httpchunk_read(conn, line_start+1,
446 gotbytes -i, &gotbytes);
447 if(r == CHUNKE_STOP) {
448 /* we're done reading chunks! */
449 infof(data, "chunk reading DONE\n");
451 /* we did the full CONNECT treatment, go to
453 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
456 infof(data, "Read %zd bytes of chunk, continue\n",
460 /* without content-length or chunked encoding, we
461 can't keep the connection alive since the close is
462 the end signal so we bail out at once instead */
468 if(200 == data->info.httpproxycode) {
470 failf(data, "Proxy CONNECT followed by %zd bytes "
471 "of opaque data. Data ignored (known bug #39)",
475 /* we did the full CONNECT treatment, go to COMPLETE */
476 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
477 break; /* breaks out of for-loop, not switch() */
480 /* keep a backup of the position we are about to blank */
481 letter = line_start[perline];
482 line_start[perline]=0; /* zero terminate the buffer */
483 if((checkprefix("WWW-Authenticate:", line_start) &&
484 (401 == k->httpcode)) ||
485 (checkprefix("Proxy-authenticate:", line_start) &&
486 (407 == k->httpcode))) {
487 result = Curl_http_input_auth(conn, k->httpcode,
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 NEGOTIATION 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 failf(data, "Received HTTP code %d from proxy after CONNECT",
572 if(closeConnection && data->req.newurl)
573 conn->bits.proxy_connect_closed = TRUE;
575 /* to back to init state */
576 conn->tunnel_state[sockindex] = TUNNEL_INIT;
578 return CURLE_RECV_ERROR;
581 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
583 /* If a proxy-authorization header was used for the proxy, then we should
584 make sure that it isn't accidentally used for the document request
585 after we've connected. So let's free and clear it here. */
586 Curl_safefree(conn->allocptr.proxyuserpwd);
587 conn->allocptr.proxyuserpwd = NULL;
589 data->state.authproxy.done = TRUE;
591 infof (data, "Proxy replied OK to CONNECT request\n");
592 data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
595 #endif /* CURL_DISABLE_PROXY */