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;
101 curl_socket_t tunnelsocket = conn->sock[sockindex];
103 bool closeConnection = FALSE;
104 bool chunked_encoding = FALSE;
108 #define SELECT_ERROR 1
109 #define SELECT_TIMEOUT 2
110 int error = SELECT_OK;
112 if(conn->tunnel_state[sockindex] == TUNNEL_COMPLETE)
113 return CURLE_OK; /* CONNECT is already completed */
115 conn->bits.proxy_connect_closed = FALSE;
118 if(TUNNEL_INIT == conn->tunnel_state[sockindex]) {
119 /* BEGIN CONNECT PHASE */
121 Curl_send_buffer *req_buffer;
123 infof(data, "Establish HTTP proxy tunnel to %s:%hu\n",
124 hostname, remote_port);
126 if(data->req.newurl) {
127 /* This only happens if we've looped here due to authentication
128 reasons, and we don't really use the newly cloned URL here
129 then. Just free() it. */
130 free(data->req.newurl);
131 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);
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 *proxyconn="";
154 const char *useragent="";
155 const char *http = (conn->proxytype == CURLPROXY_HTTP_1_0) ?
157 char *hostheader= /* host:port with IPv6 support */
158 aprintf("%s%s%s:%hu", conn->bits.ipv6_ip?"[":"",
159 hostname, conn->bits.ipv6_ip?"]":"",
163 return CURLE_OUT_OF_MEMORY;
166 if(!Curl_checkProxyheaders(conn, "Host:")) {
167 host = aprintf("Host: %s\r\n", hostheader);
171 return CURLE_OUT_OF_MEMORY;
174 if(!Curl_checkProxyheaders(conn, "Proxy-Connection:"))
175 proxyconn = "Proxy-Connection: Keep-Alive\r\n";
177 if(!Curl_checkProxyheaders(conn, "User-Agent:") &&
178 data->set.str[STRING_USERAGENT])
179 useragent = conn->allocptr.uagent;
182 Curl_add_bufferf(req_buffer,
183 "CONNECT %s HTTP/%s\r\n"
185 "%s" /* Proxy-Authorization */
186 "%s" /* User-Agent */
187 "%s", /* Proxy-Connection */
191 conn->allocptr.proxyuserpwd?
192 conn->allocptr.proxyuserpwd:"",
201 result = Curl_add_custom_headers(conn, TRUE, req_buffer);
204 /* CRLF terminate the request */
205 result = Curl_add_bufferf(req_buffer, "\r\n");
208 /* Send the connect request to the proxy */
211 Curl_add_buffer_send(req_buffer, conn,
212 &data->info.request_size, 0, sockindex);
216 failf(data, "Failed sending CONNECT to proxy");
219 Curl_safefree(req_buffer);
223 conn->tunnel_state[sockindex] = TUNNEL_CONNECT;
224 } /* END CONNECT PHASE */
226 check = Curl_timeleft(data, NULL, TRUE);
228 failf(data, "Proxy CONNECT aborted due to timeout");
229 return CURLE_RECV_ERROR;
232 if(0 == Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD, 0))
233 /* return so we'll be called again polling-style */
237 "Read response immediately from proxy CONNECT\n"));
240 /* at this point, the tunnel_connecting phase is over. */
242 { /* READING RESPONSE PHASE */
243 size_t nread; /* total size read */
244 int perline; /* count bytes per line */
250 ptr=data->state.buffer;
256 while((nread<BUFSIZE) && (keepon && !error)) {
258 check = Curl_timeleft(data, NULL, TRUE);
260 failf(data, "Proxy CONNECT aborted due to timeout");
261 error = SELECT_TIMEOUT; /* already too little time */
265 /* loop every second at least, less if the timeout is near */
266 switch (Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD,
267 check<1000L?check:1000)) {
268 case -1: /* select() error, stop reading */
269 error = SELECT_ERROR;
270 failf(data, "Proxy CONNECT aborted due to select/poll error");
272 case 0: /* timeout */
275 DEBUGASSERT(ptr+BUFSIZE-nread <= data->state.buffer+BUFSIZE+1);
276 result = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread,
278 if(result==CURLE_AGAIN)
279 continue; /* go loop yourself */
282 else if(gotbytes <= 0) {
284 if(data->set.proxyauth && data->state.authproxy.avail) {
285 /* proxy auth was requested and there was proxy auth available,
286 then deem this as "mere" proxy disconnect */
287 conn->bits.proxy_connect_closed = TRUE;
288 infof(data, "Proxy CONNECT connection closed");
291 error = SELECT_ERROR;
292 failf(data, "Proxy CONNECT aborted");
297 * We got a whole chunk of data, which can be anything from one
298 * byte to a set of lines and possibly just a piece of the last
306 /* This means we are currently ignoring a response-body */
308 nread = 0; /* make next read start over in the read buffer */
309 ptr=data->state.buffer;
311 /* A Content-Length based body: simply count down the counter
312 and make sure to break out of the loop when we're done! */
320 /* chunked-encoded body, so we need to do the chunked dance
321 properly to know when the end of the body is reached */
323 ssize_t tookcareof=0;
325 /* now parse the chunked piece of data so that we can
326 properly tell when the stream ends */
327 r = Curl_httpchunk_read(conn, ptr, gotbytes, &tookcareof);
328 if(r == CHUNKE_STOP) {
329 /* we're done reading chunks! */
330 infof(data, "chunk reading DONE\n");
332 /* we did the full CONNECT treatment, go COMPLETE */
333 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
336 infof(data, "Read %zd bytes of chunk, continue\n",
341 for(i = 0; i < gotbytes; ptr++, i++) {
342 perline++; /* amount of bytes in this line so far */
347 /* convert from the network encoding */
348 result = Curl_convert_from_network(data, line_start,
350 /* Curl_convert_from_network calls failf if unsuccessful */
354 /* output debug if that is requested */
355 if(data->set.verbose)
356 Curl_debug(data, CURLINFO_HEADER_IN,
357 line_start, (size_t)perline, conn);
359 /* send the header to the callback */
360 writetype = CLIENTWRITE_HEADER;
361 if(data->set.include_header)
362 writetype |= CLIENTWRITE_BODY;
364 result = Curl_client_write(conn, writetype, line_start,
367 data->info.header_size += (long)perline;
368 data->req.headerbytecount += (long)perline;
373 /* Newlines are CRLF, so the CR is ignored as the line isn't
374 really terminated until the LF comes. Treat a following CR
375 as end-of-headers as well.*/
377 if(('\r' == line_start[0]) ||
378 ('\n' == line_start[0])) {
379 /* end of response-headers from the proxy */
380 nread = 0; /* make next read start over in the read
382 ptr=data->state.buffer;
383 if((407 == k->httpcode) && !data->state.authproblem) {
384 /* If we get a 407 response code with content length
385 when we have no auth problem, we must ignore the
386 whole response-body */
390 infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
391 " bytes of response-body\n", cl);
393 /* remove the remaining chunk of what we already
395 cl -= (gotbytes - i);
398 /* if the whole thing was already read, we are done!
402 else if(chunked_encoding) {
404 /* We set ignorebody true here since the chunked
405 decoder function will acknowledge that. Pay
406 attention so that this is cleared again when this
408 k->ignorebody = TRUE;
409 infof(data, "%zd bytes of chunk left\n", gotbytes-i);
411 if(line_start[1] == '\n') {
412 /* this can only be a LF if the letter at index 0
418 /* now parse the chunked piece of data so that we can
419 properly tell when the stream ends */
420 r = Curl_httpchunk_read(conn, line_start+1,
421 gotbytes -i, &gotbytes);
422 if(r == CHUNKE_STOP) {
423 /* we're done reading chunks! */
424 infof(data, "chunk reading DONE\n");
426 /* we did the full CONNECT treatment, go to
428 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
431 infof(data, "Read %zd bytes of chunk, continue\n",
435 /* without content-length or chunked encoding, we
436 can't keep the connection alive since the close is
437 the end signal so we bail out at once instead */
443 if(200 == data->info.httpproxycode) {
445 failf(data, "Proxy CONNECT followed by %zd bytes "
446 "of opaque data. Data ignored (known bug #39)",
450 /* we did the full CONNECT treatment, go to COMPLETE */
451 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
452 break; /* breaks out of for-loop, not switch() */
455 /* keep a backup of the position we are about to blank */
456 letter = line_start[perline];
457 line_start[perline]=0; /* zero terminate the buffer */
458 if((checkprefix("WWW-Authenticate:", line_start) &&
459 (401 == k->httpcode)) ||
460 (checkprefix("Proxy-authenticate:", line_start) &&
461 (407 == k->httpcode))) {
463 bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
464 char *auth = Curl_copy_header_value(line_start);
466 return CURLE_OUT_OF_MEMORY;
468 result = Curl_http_input_auth(conn, proxy, auth);
475 else if(checkprefix("Content-Length:", line_start)) {
476 cl = curlx_strtoofft(line_start +
477 strlen("Content-Length:"), NULL, 10);
479 else if(Curl_compareheader(line_start,
480 "Connection:", "close"))
481 closeConnection = TRUE;
482 else if(Curl_compareheader(line_start,
483 "Transfer-Encoding:",
485 infof(data, "CONNECT responded chunked\n");
486 chunked_encoding = TRUE;
487 /* init our chunky engine */
488 Curl_httpchunk_init(conn);
490 else if(Curl_compareheader(line_start,
491 "Proxy-Connection:", "close"))
492 closeConnection = TRUE;
493 else if(2 == sscanf(line_start, "HTTP/1.%d %d",
496 /* store the HTTP code from the proxy */
497 data->info.httpproxycode = k->httpcode;
499 /* put back the letter we blanked out before */
500 line_start[perline]= letter;
502 perline=0; /* line starts over here */
503 line_start = ptr+1; /* this skips the zero byte we wrote */
509 if(Curl_pgrsUpdate(conn))
510 return CURLE_ABORTED_BY_CALLBACK;
511 } /* while there's buffer left and loop is requested */
514 return CURLE_RECV_ERROR;
516 if(data->info.httpproxycode != 200) {
517 /* Deal with the possibly already received authenticate
518 headers. 'newurl' is set to a new URL if we must loop. */
519 result = Curl_http_auth_act(conn);
524 /* the connection has been marked for closure, most likely in the
525 Curl_http_auth_act() function and thus we can kill it at once
528 closeConnection = TRUE;
531 if(closeConnection && data->req.newurl) {
532 /* Connection closed by server. Don't use it anymore */
533 Curl_closesocket(conn, conn->sock[sockindex]);
534 conn->sock[sockindex] = CURL_SOCKET_BAD;
537 } /* END READING RESPONSE PHASE */
539 /* If we are supposed to continue and request a new URL, which basically
540 * means the HTTP authentication is still going on so if the tunnel
541 * is complete we start over in INIT state */
542 if(data->req.newurl &&
543 (TUNNEL_COMPLETE == conn->tunnel_state[sockindex])) {
544 conn->tunnel_state[sockindex] = TUNNEL_INIT;
545 infof(data, "TUNNEL_STATE switched to: %d\n",
546 conn->tunnel_state[sockindex]);
549 } while(data->req.newurl);
551 if(200 != data->req.httpcode) {
552 if(closeConnection && data->req.newurl) {
553 conn->bits.proxy_connect_closed = TRUE;
554 infof(data, "Connect me again please\n");
557 if(data->req.newurl) {
558 /* this won't be used anymore for the CONNECT so free it now */
559 free(data->req.newurl);
560 data->req.newurl = NULL;
562 /* failure, close this connection to avoid re-use */
563 connclose(conn, "proxy CONNECT failure");
564 Curl_closesocket(conn, conn->sock[sockindex]);
565 conn->sock[sockindex] = CURL_SOCKET_BAD;
568 /* to back to init state */
569 conn->tunnel_state[sockindex] = TUNNEL_INIT;
571 if(conn->bits.proxy_connect_closed)
572 /* this is not an error, just part of the connection negotiation */
575 failf(data, "Received HTTP code %d from proxy after CONNECT",
577 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 */
593 conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the
597 #endif /* CURL_DISABLE_PROXY */