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)
28 #include <curl/curl.h>
29 #include "http_proxy.h"
36 #include "non-ascii.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: */
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.
92 CURLcode Curl_proxyCONNECT(struct connectdata *conn,
95 unsigned short remote_port)
98 struct SessionHandle *data=conn->data;
99 struct SingleRequest *k = &data->req;
102 data->set.timeout?data->set.timeout:PROXY_TIMEOUT; /* in milliseconds */
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 if(data->req.newurl) {
129 /* This only happens if we've looped here due to authentication
130 reasons, and we don't really use the newly cloned URL here
131 then. Just free() it. */
132 free(data->req.newurl);
133 data->req.newurl = NULL;
136 /* initialize a dynamic send-buffer */
137 req_buffer = Curl_add_buffer_init();
140 return CURLE_OUT_OF_MEMORY;
142 host_port = aprintf("%s:%hu", hostname, remote_port);
145 return CURLE_OUT_OF_MEMORY;
148 /* Setup the proxy-authorization header, if any */
149 result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
153 if(CURLE_OK == result) {
154 char *host=(char *)"";
155 const char *proxyconn="";
156 const char *useragent="";
157 const char *http = (conn->proxytype == CURLPROXY_HTTP_1_0) ?
159 char *hostheader= /* host:port with IPv6 support */
160 aprintf("%s%s%s:%hu", conn->bits.ipv6_ip?"[":"",
161 hostname, conn->bits.ipv6_ip?"]":"",
165 return CURLE_OUT_OF_MEMORY;
168 if(!Curl_checkheaders(data, "Host:")) {
169 host = aprintf("Host: %s\r\n", hostheader);
173 return CURLE_OUT_OF_MEMORY;
176 if(!Curl_checkheaders(data, "Proxy-Connection:"))
177 proxyconn = "Proxy-Connection: Keep-Alive\r\n";
179 if(!Curl_checkheaders(data, "User-Agent:") &&
180 data->set.str[STRING_USERAGENT])
181 useragent = conn->allocptr.uagent;
184 Curl_add_bufferf(req_buffer,
185 "CONNECT %s HTTP/%s\r\n"
187 "%s" /* Proxy-Authorization */
188 "%s" /* User-Agent */
189 "%s", /* Proxy-Connection */
193 conn->allocptr.proxyuserpwd?
194 conn->allocptr.proxyuserpwd:"",
202 if(CURLE_OK == result)
203 result = Curl_add_custom_headers(conn, req_buffer);
205 if(CURLE_OK == result)
206 /* CRLF terminate the request */
207 result = Curl_add_bufferf(req_buffer, "\r\n");
209 if(CURLE_OK == result) {
210 /* Send the connect request to the proxy */
213 Curl_add_buffer_send(req_buffer, conn,
214 &data->info.request_size, 0, sockindex);
218 failf(data, "Failed sending CONNECT to proxy");
221 Curl_safefree(req_buffer);
225 conn->tunnel_state[sockindex] = TUNNEL_CONNECT;
227 /* now we've issued the CONNECT and we're waiting to hear back, return
228 and get called again polling-style */
231 } /* END CONNECT PHASE */
233 { /* BEGIN NEGOTIATION PHASE */
234 size_t nread; /* total size read */
235 int perline; /* count bytes per line */
241 ptr=data->state.buffer;
248 while((nread<BUFSIZE) && (keepon && !error)) {
250 /* if timeout is requested, find out how much remaining time we have */
251 check = timeout - /* timeout time */
252 Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */
254 failf(data, "Proxy CONNECT aborted due to timeout");
255 error = SELECT_TIMEOUT; /* already too little time */
259 /* loop every second at least, less if the timeout is near */
260 switch (Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD,
261 check<1000L?check:1000)) {
262 case -1: /* select() error, stop reading */
263 error = SELECT_ERROR;
264 failf(data, "Proxy CONNECT aborted due to select/poll error");
266 case 0: /* timeout */
269 DEBUGASSERT(ptr+BUFSIZE-nread <= data->state.buffer+BUFSIZE+1);
270 result = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread,
272 if(result==CURLE_AGAIN)
273 continue; /* go loop yourself */
276 else if(gotbytes <= 0) {
278 if(data->set.proxyauth && data->state.authproxy.avail) {
279 /* proxy auth was requested and there was proxy auth available,
280 then deem this as "mere" proxy disconnect */
281 conn->bits.proxy_connect_closed = TRUE;
284 error = SELECT_ERROR;
285 failf(data, "Proxy CONNECT aborted");
290 * We got a whole chunk of data, which can be anything from one
291 * byte to a set of lines and possibly just a piece of the last
299 /* This means we are currently ignoring a response-body */
301 nread = 0; /* make next read start over in the read buffer */
302 ptr=data->state.buffer;
304 /* A Content-Length based body: simply count down the counter
305 and make sure to break out of the loop when we're done! */
313 /* chunked-encoded body, so we need to do the chunked dance
314 properly to know when the end of the body is reached */
316 ssize_t tookcareof=0;
318 /* now parse the chunked piece of data so that we can
319 properly tell when the stream ends */
320 r = Curl_httpchunk_read(conn, ptr, gotbytes, &tookcareof);
321 if(r == CHUNKE_STOP) {
322 /* we're done reading chunks! */
323 infof(data, "chunk reading DONE\n");
325 /* we did the full CONNECT treatment, go COMPLETE */
326 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
329 infof(data, "Read %zd bytes of chunk, continue\n",
334 for(i = 0; i < gotbytes; ptr++, i++) {
335 perline++; /* amount of bytes in this line so far */
340 /* convert from the network encoding */
341 result = Curl_convert_from_network(data, line_start,
343 /* Curl_convert_from_network calls failf if unsuccessful */
347 /* output debug if that is requested */
348 if(data->set.verbose)
349 Curl_debug(data, CURLINFO_HEADER_IN,
350 line_start, (size_t)perline, conn);
352 /* send the header to the callback */
353 writetype = CLIENTWRITE_HEADER;
354 if(data->set.include_header)
355 writetype |= CLIENTWRITE_BODY;
357 result = Curl_client_write(conn, writetype, line_start,
360 data->info.header_size += (long)perline;
361 data->req.headerbytecount += (long)perline;
366 /* Newlines are CRLF, so the CR is ignored as the line isn't
367 really terminated until the LF comes. Treat a following CR
368 as end-of-headers as well.*/
370 if(('\r' == line_start[0]) ||
371 ('\n' == line_start[0])) {
372 /* end of response-headers from the proxy */
373 nread = 0; /* make next read start over in the read
375 ptr=data->state.buffer;
376 if((407 == k->httpcode) && !data->state.authproblem) {
377 /* If we get a 407 response code with content length
378 when we have no auth problem, we must ignore the
379 whole response-body */
384 infof(data, "Ignore %" FORMAT_OFF_T
385 " bytes of response-body\n", cl);
386 /* remove the remaining chunk of what we already
388 cl -= (gotbytes - i);
391 /* if the whole thing was already read, we are done!
395 else if(chunked_encoding) {
397 /* We set ignorebody true here since the chunked
398 decoder function will acknowledge that. Pay
399 attention so that this is cleared again when this
401 k->ignorebody = TRUE;
402 infof(data, "%zd bytes of chunk left\n", gotbytes-i);
404 if(line_start[1] == '\n') {
405 /* this can only be a LF if the letter at index 0
411 /* now parse the chunked piece of data so that we can
412 properly tell when the stream ends */
413 r = Curl_httpchunk_read(conn, line_start+1,
414 gotbytes -i, &gotbytes);
415 if(r == CHUNKE_STOP) {
416 /* we're done reading chunks! */
417 infof(data, "chunk reading DONE\n");
419 /* we did the full CONNECT treatment, go to
421 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
424 infof(data, "Read %zd bytes of chunk, continue\n",
428 /* without content-length or chunked encoding, we
429 can't keep the connection alive since the close is
430 the end signal so we bail out at once instead */
436 if(200 == data->info.httpproxycode) {
438 failf(data, "Proxy CONNECT followed by %zd bytes "
439 "of opaque data. Data ignored (known bug #39)",
443 /* we did the full CONNECT treatment, go to COMPLETE */
444 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
445 break; /* breaks out of for-loop, not switch() */
448 /* keep a backup of the position we are about to blank */
449 letter = line_start[perline];
450 line_start[perline]=0; /* zero terminate the buffer */
451 if((checkprefix("WWW-Authenticate:", line_start) &&
452 (401 == k->httpcode)) ||
453 (checkprefix("Proxy-authenticate:", line_start) &&
454 (407 == k->httpcode))) {
455 result = Curl_http_input_auth(conn, k->httpcode,
460 else if(checkprefix("Content-Length:", line_start)) {
461 cl = curlx_strtoofft(line_start +
462 strlen("Content-Length:"), NULL, 10);
464 else if(Curl_compareheader(line_start,
465 "Connection:", "close"))
466 closeConnection = TRUE;
467 else if(Curl_compareheader(line_start,
468 "Transfer-Encoding:",
470 infof(data, "CONNECT responded chunked\n");
471 chunked_encoding = TRUE;
472 /* init our chunky engine */
473 Curl_httpchunk_init(conn);
475 else if(Curl_compareheader(line_start,
476 "Proxy-Connection:", "close"))
477 closeConnection = TRUE;
478 else if(2 == sscanf(line_start, "HTTP/1.%d %d",
481 /* store the HTTP code from the proxy */
482 data->info.httpproxycode = k->httpcode;
484 /* put back the letter we blanked out before */
485 line_start[perline]= letter;
487 perline=0; /* line starts over here */
488 line_start = ptr+1; /* this skips the zero byte we wrote */
494 if(Curl_pgrsUpdate(conn))
495 return CURLE_ABORTED_BY_CALLBACK;
496 } /* while there's buffer left and loop is requested */
499 return CURLE_RECV_ERROR;
501 if(data->info.httpproxycode != 200) {
502 /* Deal with the possibly already received authenticate
503 headers. 'newurl' is set to a new URL if we must loop. */
504 result = Curl_http_auth_act(conn);
509 /* the connection has been marked for closure, most likely in the
510 Curl_http_auth_act() function and thus we can kill it at once
513 closeConnection = TRUE;
516 if(closeConnection && data->req.newurl) {
517 /* Connection closed by server. Don't use it anymore */
518 Curl_closesocket(conn, conn->sock[sockindex]);
519 conn->sock[sockindex] = CURL_SOCKET_BAD;
522 } /* END NEGOTIATION PHASE */
524 /* If we are supposed to continue and request a new URL, which basically
525 * means the HTTP authentication is still going on so if the tunnel
526 * is complete we start over in INIT state */
527 if(data->req.newurl &&
528 (TUNNEL_COMPLETE == conn->tunnel_state[sockindex])) {
529 conn->tunnel_state[sockindex] = TUNNEL_INIT;
530 infof(data, "TUNNEL_STATE switched to: %d\n",
531 conn->tunnel_state[sockindex]);
534 } while(data->req.newurl);
536 if(200 != data->req.httpcode) {
537 failf(data, "Received HTTP code %d from proxy after CONNECT",
540 if(closeConnection && data->req.newurl)
541 conn->bits.proxy_connect_closed = TRUE;
543 if(data->req.newurl) {
544 /* this won't be used anymore for the CONNECT so free it now */
545 free(data->req.newurl);
546 data->req.newurl = NULL;
549 /* to back to init state */
550 conn->tunnel_state[sockindex] = TUNNEL_INIT;
552 return CURLE_RECV_ERROR;
555 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
557 /* If a proxy-authorization header was used for the proxy, then we should
558 make sure that it isn't accidentally used for the document request
559 after we've connected. So let's free and clear it here. */
560 Curl_safefree(conn->allocptr.proxyuserpwd);
561 conn->allocptr.proxyuserpwd = NULL;
563 data->state.authproxy.done = TRUE;
565 infof (data, "Proxy replied OK to CONNECT request\n");
566 data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
567 conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the
571 #endif /* CURL_DISABLE_PROXY */