1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2012, 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 ***************************************************************************/
25 #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
32 #include <curl/curl.h>
33 #include "http_proxy.h"
40 #include "non-ascii.h"
43 #define _MPRINTF_REPLACE /* use our functions only */
44 #include <curl/mprintf.h>
48 #include "curl_memory.h"
49 /* The last #include file should be: */
52 CURLcode Curl_proxy_connect(struct connectdata *conn)
54 if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
55 #ifndef CURL_DISABLE_PROXY
56 /* for [protocol] tunneled through HTTP proxy */
57 struct HTTP http_proxy;
62 /* We want "seamless" operations through HTTP proxy tunnel */
64 /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the
65 * member conn->proto.http; we want [protocol] through HTTP and we have
66 * to change the member temporarily for connecting to the HTTP
67 * proxy. After Curl_proxyCONNECT we have to set back the member to the
70 * This function might be called several times in the multi interface case
71 * if the proxy's CONNTECT response is not instant.
73 prot_save = conn->data->state.proto.generic;
74 memset(&http_proxy, 0, sizeof(http_proxy));
75 conn->data->state.proto.http = &http_proxy;
76 conn->bits.close = FALSE;
77 result = Curl_proxyCONNECT(conn, FIRSTSOCKET,
78 conn->host.name, conn->remote_port);
79 conn->data->state.proto.generic = prot_save;
80 if(CURLE_OK != result)
83 return CURLE_NOT_BUILT_IN;
86 /* no HTTP tunnel proxy, just return */
91 * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
92 * function will issue the necessary commands to get a seamless tunnel through
93 * this proxy. After that, the socket can be used just as a normal socket.
95 * This badly needs to be rewritten. CONNECT should be sent and dealt with
96 * like any ordinary HTTP request, and not specially crafted like this. This
97 * function only remains here like this for now since the rewrite is a bit too
98 * much work to do at the moment.
100 * This function is BLOCKING which is nasty for all multi interface using apps.
103 CURLcode Curl_proxyCONNECT(struct connectdata *conn,
105 const char *hostname,
106 unsigned short remote_port)
109 struct SessionHandle *data=conn->data;
110 struct SingleRequest *k = &data->req;
113 data->set.timeout?data->set.timeout:PROXY_TIMEOUT; /* in milliseconds */
114 curl_socket_t tunnelsocket = conn->sock[sockindex];
116 bool closeConnection = FALSE;
117 bool chunked_encoding = FALSE;
121 #define SELECT_ERROR 1
122 #define SELECT_TIMEOUT 2
123 int error = SELECT_OK;
125 if(conn->tunnel_state[sockindex] == TUNNEL_COMPLETE)
126 return CURLE_OK; /* CONNECT is already completed */
128 conn->bits.proxy_connect_closed = FALSE;
131 if(TUNNEL_INIT == conn->tunnel_state[sockindex]) {
132 /* BEGIN CONNECT PHASE */
134 Curl_send_buffer *req_buffer;
136 infof(data, "Establish HTTP proxy tunnel to %s:%hu\n",
137 hostname, remote_port);
139 if(data->req.newurl) {
140 /* This only happens if we've looped here due to authentication
141 reasons, and we don't really use the newly cloned URL here
142 then. Just free() it. */
143 free(data->req.newurl);
144 data->req.newurl = NULL;
147 /* initialize a dynamic send-buffer */
148 req_buffer = Curl_add_buffer_init();
151 return CURLE_OUT_OF_MEMORY;
153 host_port = aprintf("%s:%hu", hostname, remote_port);
156 return CURLE_OUT_OF_MEMORY;
159 /* Setup the proxy-authorization header, if any */
160 result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
164 if(CURLE_OK == result) {
165 char *host=(char *)"";
166 const char *proxyconn="";
167 const char *useragent="";
168 const char *http = (conn->proxytype == CURLPROXY_HTTP_1_0) ?
170 char *hostheader= /* host:port with IPv6 support */
171 aprintf("%s%s%s:%hu", conn->bits.ipv6_ip?"[":"",
172 hostname, conn->bits.ipv6_ip?"]":"",
176 return CURLE_OUT_OF_MEMORY;
179 if(!Curl_checkheaders(data, "Host:")) {
180 host = aprintf("Host: %s\r\n", hostheader);
184 return CURLE_OUT_OF_MEMORY;
187 if(!Curl_checkheaders(data, "Proxy-Connection:"))
188 proxyconn = "Proxy-Connection: Keep-Alive\r\n";
190 if(!Curl_checkheaders(data, "User-Agent:") &&
191 data->set.str[STRING_USERAGENT])
192 useragent = conn->allocptr.uagent;
195 Curl_add_bufferf(req_buffer,
196 "CONNECT %s HTTP/%s\r\n"
198 "%s" /* Proxy-Authorization */
199 "%s" /* User-Agent */
200 "%s", /* Proxy-Connection */
204 conn->allocptr.proxyuserpwd?
205 conn->allocptr.proxyuserpwd:"",
213 if(CURLE_OK == result)
214 result = Curl_add_custom_headers(conn, req_buffer);
216 if(CURLE_OK == result)
217 /* CRLF terminate the request */
218 result = Curl_add_bufferf(req_buffer, "\r\n");
220 if(CURLE_OK == result) {
221 /* Send the connect request to the proxy */
224 Curl_add_buffer_send(req_buffer, conn,
225 &data->info.request_size, 0, sockindex);
229 failf(data, "Failed sending CONNECT to proxy");
232 Curl_safefree(req_buffer);
236 conn->tunnel_state[sockindex] = TUNNEL_CONNECT;
237 } /* END CONNECT PHASE */
239 /* now we've issued the CONNECT and we're waiting to hear back -
240 we try not to block here in multi-mode because that might be a LONG
241 wait if the proxy cannot connect-through to the remote host. */
243 /* if timeout is requested, find out how much remaining time we have */
244 check = timeout - /* timeout time */
245 Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */
247 failf(data, "Proxy CONNECT aborted due to timeout");
248 return CURLE_RECV_ERROR;
251 /* if we're in multi-mode and we would block, return instead for a retry */
252 if(Curl_if_multi == data->state.used_interface) {
253 if(0 == Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD, 0))
254 /* return so we'll be called again polling-style */
258 "Multi mode finished polling for response from "
263 DEBUGF(infof(data, "Easy mode waiting response from proxy CONNECT\n"));
266 /* at this point, either:
267 1) we're in easy-mode and so it's okay to block waiting for a CONNECT
269 2) we're in multi-mode and we didn't block - it's either an error or we
270 now have some data waiting.
271 In any case, the tunnel_connecting phase is over. */
273 { /* BEGIN NEGOTIATION PHASE */
274 size_t nread; /* total size read */
275 int perline; /* count bytes per line */
281 ptr=data->state.buffer;
288 while((nread<BUFSIZE) && (keepon && !error)) {
290 /* if timeout is requested, find out how much remaining time we have */
291 check = timeout - /* timeout time */
292 Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */
294 failf(data, "Proxy CONNECT aborted due to timeout");
295 error = SELECT_TIMEOUT; /* already too little time */
299 /* loop every second at least, less if the timeout is near */
300 switch (Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD,
301 check<1000L?check:1000)) {
302 case -1: /* select() error, stop reading */
303 error = SELECT_ERROR;
304 failf(data, "Proxy CONNECT aborted due to select/poll error");
306 case 0: /* timeout */
309 DEBUGASSERT(ptr+BUFSIZE-nread <= data->state.buffer+BUFSIZE+1);
310 result = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread,
312 if(result==CURLE_AGAIN)
313 continue; /* go loop yourself */
316 else if(gotbytes <= 0) {
318 if(data->set.proxyauth && data->state.authproxy.avail) {
319 /* proxy auth was requested and there was proxy auth available,
320 then deem this as "mere" proxy disconnect */
321 conn->bits.proxy_connect_closed = TRUE;
324 error = SELECT_ERROR;
325 failf(data, "Proxy CONNECT aborted");
330 * We got a whole chunk of data, which can be anything from one
331 * byte to a set of lines and possibly just a piece of the last
339 /* This means we are currently ignoring a response-body */
341 nread = 0; /* make next read start over in the read buffer */
342 ptr=data->state.buffer;
344 /* A Content-Length based body: simply count down the counter
345 and make sure to break out of the loop when we're done! */
353 /* chunked-encoded body, so we need to do the chunked dance
354 properly to know when the end of the body is reached */
356 ssize_t tookcareof=0;
358 /* now parse the chunked piece of data so that we can
359 properly tell when the stream ends */
360 r = Curl_httpchunk_read(conn, ptr, gotbytes, &tookcareof);
361 if(r == CHUNKE_STOP) {
362 /* we're done reading chunks! */
363 infof(data, "chunk reading DONE\n");
365 /* we did the full CONNECT treatment, go COMPLETE */
366 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
369 infof(data, "Read %zd bytes of chunk, continue\n",
374 for(i = 0; i < gotbytes; ptr++, i++) {
375 perline++; /* amount of bytes in this line so far */
380 /* convert from the network encoding */
381 result = Curl_convert_from_network(data, line_start,
383 /* Curl_convert_from_network calls failf if unsuccessful */
387 /* output debug if that is requested */
388 if(data->set.verbose)
389 Curl_debug(data, CURLINFO_HEADER_IN,
390 line_start, (size_t)perline, conn);
392 /* send the header to the callback */
393 writetype = CLIENTWRITE_HEADER;
394 if(data->set.include_header)
395 writetype |= CLIENTWRITE_BODY;
397 result = Curl_client_write(conn, writetype, line_start,
402 /* Newlines are CRLF, so the CR is ignored as the line isn't
403 really terminated until the LF comes. Treat a following CR
404 as end-of-headers as well.*/
406 if(('\r' == line_start[0]) ||
407 ('\n' == line_start[0])) {
408 /* end of response-headers from the proxy */
409 nread = 0; /* make next read start over in the read
411 ptr=data->state.buffer;
412 if((407 == k->httpcode) && !data->state.authproblem) {
413 /* If we get a 407 response code with content length
414 when we have no auth problem, we must ignore the
415 whole response-body */
420 infof(data, "Ignore %" FORMAT_OFF_T
421 " bytes of response-body\n", cl);
422 /* remove the remaining chunk of what we already
424 cl -= (gotbytes - i);
427 /* if the whole thing was already read, we are done!
431 else if(chunked_encoding) {
433 /* We set ignorebody true here since the chunked
434 decoder function will acknowledge that. Pay
435 attention so that this is cleared again when this
437 k->ignorebody = TRUE;
438 infof(data, "%zd bytes of chunk left\n", gotbytes-i);
440 if(line_start[1] == '\n') {
441 /* this can only be a LF if the letter at index 0
447 /* now parse the chunked piece of data so that we can
448 properly tell when the stream ends */
449 r = Curl_httpchunk_read(conn, line_start+1,
450 gotbytes -i, &gotbytes);
451 if(r == CHUNKE_STOP) {
452 /* we're done reading chunks! */
453 infof(data, "chunk reading DONE\n");
455 /* we did the full CONNECT treatment, go to
457 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
460 infof(data, "Read %zd bytes of chunk, continue\n",
464 /* without content-length or chunked encoding, we
465 can't keep the connection alive since the close is
466 the end signal so we bail out at once instead */
472 if(200 == data->info.httpproxycode) {
474 failf(data, "Proxy CONNECT followed by %zd bytes "
475 "of opaque data. Data ignored (known bug #39)",
479 /* we did the full CONNECT treatment, go to COMPLETE */
480 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
481 break; /* breaks out of for-loop, not switch() */
484 /* keep a backup of the position we are about to blank */
485 letter = line_start[perline];
486 line_start[perline]=0; /* zero terminate the buffer */
487 if((checkprefix("WWW-Authenticate:", line_start) &&
488 (401 == k->httpcode)) ||
489 (checkprefix("Proxy-authenticate:", line_start) &&
490 (407 == k->httpcode))) {
491 result = Curl_http_input_auth(conn, k->httpcode,
496 else if(checkprefix("Content-Length:", line_start)) {
497 cl = curlx_strtoofft(line_start +
498 strlen("Content-Length:"), NULL, 10);
500 else if(Curl_compareheader(line_start,
501 "Connection:", "close"))
502 closeConnection = TRUE;
503 else if(Curl_compareheader(line_start,
504 "Transfer-Encoding:",
506 infof(data, "CONNECT responded chunked\n");
507 chunked_encoding = TRUE;
508 /* init our chunky engine */
509 Curl_httpchunk_init(conn);
511 else if(Curl_compareheader(line_start,
512 "Proxy-Connection:", "close"))
513 closeConnection = TRUE;
514 else if(2 == sscanf(line_start, "HTTP/1.%d %d",
517 /* store the HTTP code from the proxy */
518 data->info.httpproxycode = k->httpcode;
520 /* put back the letter we blanked out before */
521 line_start[perline]= letter;
523 perline=0; /* line starts over here */
524 line_start = ptr+1; /* this skips the zero byte we wrote */
530 if(Curl_pgrsUpdate(conn))
531 return CURLE_ABORTED_BY_CALLBACK;
532 } /* while there's buffer left and loop is requested */
535 return CURLE_RECV_ERROR;
537 if(data->info.httpproxycode != 200) {
538 /* Deal with the possibly already received authenticate
539 headers. 'newurl' is set to a new URL if we must loop. */
540 result = Curl_http_auth_act(conn);
545 /* the connection has been marked for closure, most likely in the
546 Curl_http_auth_act() function and thus we can kill it at once
549 closeConnection = TRUE;
552 if(closeConnection && data->req.newurl) {
553 /* Connection closed by server. Don't use it anymore */
554 Curl_closesocket(conn, conn->sock[sockindex]);
555 conn->sock[sockindex] = CURL_SOCKET_BAD;
558 } /* END NEGOTIATION PHASE */
560 /* If we are supposed to continue and request a new URL, which basically
561 * means the HTTP authentication is still going on so if the tunnel
562 * is complete we start over in INIT state */
563 if(data->req.newurl &&
564 (TUNNEL_COMPLETE == conn->tunnel_state[sockindex])) {
565 conn->tunnel_state[sockindex] = TUNNEL_INIT;
566 infof(data, "TUNNEL_STATE switched to: %d\n",
567 conn->tunnel_state[sockindex]);
570 } while(data->req.newurl);
572 if(200 != data->req.httpcode) {
573 failf(data, "Received HTTP code %d from proxy after CONNECT",
576 if(closeConnection && data->req.newurl)
577 conn->bits.proxy_connect_closed = TRUE;
579 /* to back to init state */
580 conn->tunnel_state[sockindex] = TUNNEL_INIT;
582 return CURLE_RECV_ERROR;
585 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
587 /* If a proxy-authorization header was used for the proxy, then we should
588 make sure that it isn't accidentally used for the document request
589 after we've connected. So let's free and clear it here. */
590 Curl_safefree(conn->allocptr.proxyuserpwd);
591 conn->allocptr.proxyuserpwd = NULL;
593 data->state.authproxy.done = TRUE;
595 infof (data, "Proxy replied OK to CONNECT request\n");
596 data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
599 #endif /* CURL_DISABLE_PROXY */