1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2014, 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->req.protop;
70 memset(&http_proxy, 0, sizeof(http_proxy));
71 conn->data->req.protop = &http_proxy;
72 connkeep(conn, "HTTP proxy CONNECT");
73 result = Curl_proxyCONNECT(conn, FIRSTSOCKET,
74 conn->host.name, conn->remote_port);
75 conn->data->req.protop = 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,
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_checkProxyheaders(conn, "Host:")) {
169 host = aprintf("Host: %s\r\n", hostheader);
173 return CURLE_OUT_OF_MEMORY;
176 if(!Curl_checkProxyheaders(conn, "Proxy-Connection:"))
177 proxyconn = "Proxy-Connection: Keep-Alive\r\n";
179 if(!Curl_checkProxyheaders(conn, "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, TRUE, 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 */
383 infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
384 " 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))) {
456 bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
457 char *auth = Curl_copy_header_value(line_start);
459 return CURLE_OUT_OF_MEMORY;
461 result = Curl_http_input_auth(conn, proxy, auth);
468 else if(checkprefix("Content-Length:", line_start)) {
469 cl = curlx_strtoofft(line_start +
470 strlen("Content-Length:"), NULL, 10);
472 else if(Curl_compareheader(line_start,
473 "Connection:", "close"))
474 closeConnection = TRUE;
475 else if(Curl_compareheader(line_start,
476 "Transfer-Encoding:",
478 infof(data, "CONNECT responded chunked\n");
479 chunked_encoding = TRUE;
480 /* init our chunky engine */
481 Curl_httpchunk_init(conn);
483 else if(Curl_compareheader(line_start,
484 "Proxy-Connection:", "close"))
485 closeConnection = TRUE;
486 else if(2 == sscanf(line_start, "HTTP/1.%d %d",
489 /* store the HTTP code from the proxy */
490 data->info.httpproxycode = k->httpcode;
492 /* put back the letter we blanked out before */
493 line_start[perline]= letter;
495 perline=0; /* line starts over here */
496 line_start = ptr+1; /* this skips the zero byte we wrote */
502 if(Curl_pgrsUpdate(conn))
503 return CURLE_ABORTED_BY_CALLBACK;
504 } /* while there's buffer left and loop is requested */
507 return CURLE_RECV_ERROR;
509 if(data->info.httpproxycode != 200) {
510 /* Deal with the possibly already received authenticate
511 headers. 'newurl' is set to a new URL if we must loop. */
512 result = Curl_http_auth_act(conn);
517 /* the connection has been marked for closure, most likely in the
518 Curl_http_auth_act() function and thus we can kill it at once
521 closeConnection = TRUE;
524 if(closeConnection && data->req.newurl) {
525 /* Connection closed by server. Don't use it anymore */
526 Curl_closesocket(conn, conn->sock[sockindex]);
527 conn->sock[sockindex] = CURL_SOCKET_BAD;
530 } /* END NEGOTIATION PHASE */
532 /* If we are supposed to continue and request a new URL, which basically
533 * means the HTTP authentication is still going on so if the tunnel
534 * is complete we start over in INIT state */
535 if(data->req.newurl &&
536 (TUNNEL_COMPLETE == conn->tunnel_state[sockindex])) {
537 conn->tunnel_state[sockindex] = TUNNEL_INIT;
538 infof(data, "TUNNEL_STATE switched to: %d\n",
539 conn->tunnel_state[sockindex]);
542 } while(data->req.newurl);
544 if(200 != data->req.httpcode) {
545 failf(data, "Received HTTP code %d from proxy after CONNECT",
548 if(closeConnection && data->req.newurl)
549 conn->bits.proxy_connect_closed = TRUE;
551 if(data->req.newurl) {
552 /* this won't be used anymore for the CONNECT so free it now */
553 free(data->req.newurl);
554 data->req.newurl = NULL;
557 /* to back to init state */
558 conn->tunnel_state[sockindex] = TUNNEL_INIT;
560 return CURLE_RECV_ERROR;
563 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
565 /* If a proxy-authorization header was used for the proxy, then we should
566 make sure that it isn't accidentally used for the document request
567 after we've connected. So let's free and clear it here. */
568 Curl_safefree(conn->allocptr.proxyuserpwd);
569 conn->allocptr.proxyuserpwd = NULL;
571 data->state.authproxy.done = TRUE;
573 infof (data, "Proxy replied OK to CONNECT request\n");
574 data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
575 conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the
579 #endif /* CURL_DISABLE_PROXY */