VirtualBox

source: vbox/trunk/src/libs/curl-7.64.0/lib/http_proxy.c@ 95219

Last change on this file since 95219 was 85671, checked in by vboxsync, 4 years ago

Export out internal curl copy to make it a lot simpler to build VBox (OSE) on Windows. bugref:9814

  • Property svn:eol-style set to native
File size: 22.4 KB
Line 
1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
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 https://curl.haxx.se/docs/copyright.html.
13 *
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.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ***************************************************************************/
22
23#include "curl_setup.h"
24
25#include "http_proxy.h"
26
27#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
28
29#include <curl/curl.h>
30#include "sendf.h"
31#include "http.h"
32#include "url.h"
33#include "select.h"
34#include "progress.h"
35#include "non-ascii.h"
36#include "connect.h"
37#include "curlx.h"
38#include "vtls/vtls.h"
39
40/* The last 3 #include files should be in this order */
41#include "curl_printf.h"
42#include "curl_memory.h"
43#include "memdebug.h"
44
45/*
46 * Perform SSL initialization for HTTPS proxy. Sets
47 * proxy_ssl_connected connection bit when complete. Can be
48 * called multiple times.
49 */
50static CURLcode https_proxy_connect(struct connectdata *conn, int sockindex)
51{
52#ifdef USE_SSL
53 CURLcode result = CURLE_OK;
54 DEBUGASSERT(conn->http_proxy.proxytype == CURLPROXY_HTTPS);
55 if(!conn->bits.proxy_ssl_connected[sockindex]) {
56 /* perform SSL initialization for this socket */
57 result =
58 Curl_ssl_connect_nonblocking(conn, sockindex,
59 &conn->bits.proxy_ssl_connected[sockindex]);
60 if(result)
61 conn->bits.close = TRUE; /* a failed connection is marked for closure to
62 prevent (bad) re-use or similar */
63 }
64 return result;
65#else
66 (void) conn;
67 (void) sockindex;
68 return CURLE_NOT_BUILT_IN;
69#endif
70}
71
72CURLcode Curl_proxy_connect(struct connectdata *conn, int sockindex)
73{
74 if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) {
75 const CURLcode result = https_proxy_connect(conn, sockindex);
76 if(result)
77 return result;
78 if(!conn->bits.proxy_ssl_connected[sockindex])
79 return result; /* wait for HTTPS proxy SSL initialization to complete */
80 }
81
82 if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
83#ifndef CURL_DISABLE_PROXY
84 /* for [protocol] tunneled through HTTP proxy */
85 struct HTTP http_proxy;
86 void *prot_save;
87 const char *hostname;
88 int remote_port;
89 CURLcode result;
90
91 /* BLOCKING */
92 /* We want "seamless" operations through HTTP proxy tunnel */
93
94 /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the
95 * member conn->proto.http; we want [protocol] through HTTP and we have
96 * to change the member temporarily for connecting to the HTTP
97 * proxy. After Curl_proxyCONNECT we have to set back the member to the
98 * original pointer
99 *
100 * This function might be called several times in the multi interface case
101 * if the proxy's CONNECT response is not instant.
102 */
103 prot_save = conn->data->req.protop;
104 memset(&http_proxy, 0, sizeof(http_proxy));
105 conn->data->req.protop = &http_proxy;
106 connkeep(conn, "HTTP proxy CONNECT");
107
108 /* for the secondary socket (FTP), use the "connect to host"
109 * but ignore the "connect to port" (use the secondary port)
110 */
111
112 if(conn->bits.conn_to_host)
113 hostname = conn->conn_to_host.name;
114 else if(sockindex == SECONDARYSOCKET)
115 hostname = conn->secondaryhostname;
116 else
117 hostname = conn->host.name;
118
119 if(sockindex == SECONDARYSOCKET)
120 remote_port = conn->secondary_port;
121 else if(conn->bits.conn_to_port)
122 remote_port = conn->conn_to_port;
123 else
124 remote_port = conn->remote_port;
125 result = Curl_proxyCONNECT(conn, sockindex, hostname, remote_port);
126 conn->data->req.protop = prot_save;
127 if(CURLE_OK != result)
128 return result;
129 Curl_safefree(conn->allocptr.proxyuserpwd);
130#else
131 return CURLE_NOT_BUILT_IN;
132#endif
133 }
134 /* no HTTP tunnel proxy, just return */
135 return CURLE_OK;
136}
137
138bool Curl_connect_complete(struct connectdata *conn)
139{
140 return !conn->connect_state ||
141 (conn->connect_state->tunnel_state == TUNNEL_COMPLETE);
142}
143
144bool Curl_connect_ongoing(struct connectdata *conn)
145{
146 return conn->connect_state &&
147 (conn->connect_state->tunnel_state != TUNNEL_COMPLETE);
148}
149
150static CURLcode connect_init(struct connectdata *conn, bool reinit)
151{
152 struct http_connect_state *s;
153 if(!reinit) {
154 DEBUGASSERT(!conn->connect_state);
155 s = calloc(1, sizeof(struct http_connect_state));
156 if(!s)
157 return CURLE_OUT_OF_MEMORY;
158 infof(conn->data, "allocate connect buffer!\n");
159 conn->connect_state = s;
160 }
161 else {
162 DEBUGASSERT(conn->connect_state);
163 s = conn->connect_state;
164 }
165 s->tunnel_state = TUNNEL_INIT;
166 s->keepon = TRUE;
167 s->line_start = s->connect_buffer;
168 s->ptr = s->line_start;
169 s->cl = 0;
170 s->close_connection = FALSE;
171 return CURLE_OK;
172}
173
174static void connect_done(struct connectdata *conn)
175{
176 struct http_connect_state *s = conn->connect_state;
177 s->tunnel_state = TUNNEL_COMPLETE;
178 infof(conn->data, "CONNECT phase completed!\n");
179}
180
181static CURLcode CONNECT(struct connectdata *conn,
182 int sockindex,
183 const char *hostname,
184 int remote_port)
185{
186 int subversion = 0;
187 struct Curl_easy *data = conn->data;
188 struct SingleRequest *k = &data->req;
189 CURLcode result;
190 curl_socket_t tunnelsocket = conn->sock[sockindex];
191 struct http_connect_state *s = conn->connect_state;
192
193#define SELECT_OK 0
194#define SELECT_ERROR 1
195
196 if(Curl_connect_complete(conn))
197 return CURLE_OK; /* CONNECT is already completed */
198
199 conn->bits.proxy_connect_closed = FALSE;
200
201 do {
202 timediff_t check;
203 if(TUNNEL_INIT == s->tunnel_state) {
204 /* BEGIN CONNECT PHASE */
205 char *host_port;
206 Curl_send_buffer *req_buffer;
207
208 infof(data, "Establish HTTP proxy tunnel to %s:%d\n",
209 hostname, remote_port);
210
211 /* This only happens if we've looped here due to authentication
212 reasons, and we don't really use the newly cloned URL here
213 then. Just free() it. */
214 free(data->req.newurl);
215 data->req.newurl = NULL;
216
217 /* initialize a dynamic send-buffer */
218 req_buffer = Curl_add_buffer_init();
219
220 if(!req_buffer)
221 return CURLE_OUT_OF_MEMORY;
222
223 host_port = aprintf("%s:%d", hostname, remote_port);
224 if(!host_port) {
225 Curl_add_buffer_free(&req_buffer);
226 return CURLE_OUT_OF_MEMORY;
227 }
228
229 /* Setup the proxy-authorization header, if any */
230 result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
231
232 free(host_port);
233
234 if(!result) {
235 char *host = NULL;
236 const char *proxyconn = "";
237 const char *useragent = "";
238 const char *http = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ?
239 "1.0" : "1.1";
240 bool ipv6_ip = conn->bits.ipv6_ip;
241 char *hostheader;
242
243 /* the hostname may be different */
244 if(hostname != conn->host.name)
245 ipv6_ip = (strchr(hostname, ':') != NULL);
246 hostheader = /* host:port with IPv6 support */
247 aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
248 remote_port);
249 if(!hostheader) {
250 Curl_add_buffer_free(&req_buffer);
251 return CURLE_OUT_OF_MEMORY;
252 }
253
254 if(!Curl_checkProxyheaders(conn, "Host")) {
255 host = aprintf("Host: %s\r\n", hostheader);
256 if(!host) {
257 free(hostheader);
258 Curl_add_buffer_free(&req_buffer);
259 return CURLE_OUT_OF_MEMORY;
260 }
261 }
262 if(!Curl_checkProxyheaders(conn, "Proxy-Connection"))
263 proxyconn = "Proxy-Connection: Keep-Alive\r\n";
264
265 if(!Curl_checkProxyheaders(conn, "User-Agent") &&
266 data->set.str[STRING_USERAGENT])
267 useragent = conn->allocptr.uagent;
268
269 result =
270 Curl_add_bufferf(&req_buffer,
271 "CONNECT %s HTTP/%s\r\n"
272 "%s" /* Host: */
273 "%s" /* Proxy-Authorization */
274 "%s" /* User-Agent */
275 "%s", /* Proxy-Connection */
276 hostheader,
277 http,
278 host?host:"",
279 conn->allocptr.proxyuserpwd?
280 conn->allocptr.proxyuserpwd:"",
281 useragent,
282 proxyconn);
283
284 if(host)
285 free(host);
286 free(hostheader);
287
288 if(!result)
289 result = Curl_add_custom_headers(conn, TRUE, req_buffer);
290
291 if(!result)
292 /* CRLF terminate the request */
293 result = Curl_add_bufferf(&req_buffer, "\r\n");
294
295 if(!result) {
296 /* Send the connect request to the proxy */
297 /* BLOCKING */
298 result =
299 Curl_add_buffer_send(&req_buffer, conn,
300 &data->info.request_size, 0, sockindex);
301 }
302 req_buffer = NULL;
303 if(result)
304 failf(data, "Failed sending CONNECT to proxy");
305 }
306
307 Curl_add_buffer_free(&req_buffer);
308 if(result)
309 return result;
310
311 s->tunnel_state = TUNNEL_CONNECT;
312 s->perline = 0;
313 } /* END CONNECT PHASE */
314
315 check = Curl_timeleft(data, NULL, TRUE);
316 if(check <= 0) {
317 failf(data, "Proxy CONNECT aborted due to timeout");
318 return CURLE_OPERATION_TIMEDOUT;
319 }
320
321 if(!Curl_conn_data_pending(conn, sockindex))
322 /* return so we'll be called again polling-style */
323 return CURLE_OK;
324
325 /* at this point, the tunnel_connecting phase is over. */
326
327 { /* READING RESPONSE PHASE */
328 int error = SELECT_OK;
329
330 while(s->keepon && !error) {
331 ssize_t gotbytes;
332
333 /* make sure we have space to read more data */
334 if(s->ptr >= &s->connect_buffer[CONNECT_BUFFER_SIZE]) {
335 failf(data, "CONNECT response too large!");
336 return CURLE_RECV_ERROR;
337 }
338
339 /* Read one byte at a time to avoid a race condition. Wait at most one
340 second before looping to ensure continuous pgrsUpdates. */
341 result = Curl_read(conn, tunnelsocket, s->ptr, 1, &gotbytes);
342 if(result == CURLE_AGAIN)
343 /* socket buffer drained, return */
344 return CURLE_OK;
345
346 if(Curl_pgrsUpdate(conn))
347 return CURLE_ABORTED_BY_CALLBACK;
348
349 if(result) {
350 s->keepon = FALSE;
351 break;
352 }
353 else if(gotbytes <= 0) {
354 if(data->set.proxyauth && data->state.authproxy.avail) {
355 /* proxy auth was requested and there was proxy auth available,
356 then deem this as "mere" proxy disconnect */
357 conn->bits.proxy_connect_closed = TRUE;
358 infof(data, "Proxy CONNECT connection closed\n");
359 }
360 else {
361 error = SELECT_ERROR;
362 failf(data, "Proxy CONNECT aborted");
363 }
364 s->keepon = FALSE;
365 break;
366 }
367
368
369 if(s->keepon > TRUE) {
370 /* This means we are currently ignoring a response-body */
371
372 s->ptr = s->connect_buffer;
373 if(s->cl) {
374 /* A Content-Length based body: simply count down the counter
375 and make sure to break out of the loop when we're done! */
376 s->cl--;
377 if(s->cl <= 0) {
378 s->keepon = FALSE;
379 s->tunnel_state = TUNNEL_COMPLETE;
380 break;
381 }
382 }
383 else {
384 /* chunked-encoded body, so we need to do the chunked dance
385 properly to know when the end of the body is reached */
386 CHUNKcode r;
387 ssize_t tookcareof = 0;
388
389 /* now parse the chunked piece of data so that we can
390 properly tell when the stream ends */
391 r = Curl_httpchunk_read(conn, s->ptr, 1, &tookcareof);
392 if(r == CHUNKE_STOP) {
393 /* we're done reading chunks! */
394 infof(data, "chunk reading DONE\n");
395 s->keepon = FALSE;
396 /* we did the full CONNECT treatment, go COMPLETE */
397 s->tunnel_state = TUNNEL_COMPLETE;
398 }
399 }
400 continue;
401 }
402
403 s->perline++; /* amount of bytes in this line so far */
404
405 /* if this is not the end of a header line then continue */
406 if(*s->ptr != 0x0a) {
407 s->ptr++;
408 continue;
409 }
410
411 /* convert from the network encoding */
412 result = Curl_convert_from_network(data, s->line_start,
413 (size_t)s->perline);
414 /* Curl_convert_from_network calls failf if unsuccessful */
415 if(result)
416 return result;
417
418 /* output debug if that is requested */
419 if(data->set.verbose)
420 Curl_debug(data, CURLINFO_HEADER_IN,
421 s->line_start, (size_t)s->perline);
422
423 if(!data->set.suppress_connect_headers) {
424 /* send the header to the callback */
425 int writetype = CLIENTWRITE_HEADER;
426 if(data->set.include_header)
427 writetype |= CLIENTWRITE_BODY;
428
429 result = Curl_client_write(conn, writetype,
430 s->line_start, s->perline);
431 if(result)
432 return result;
433 }
434
435 data->info.header_size += (long)s->perline;
436 data->req.headerbytecount += (long)s->perline;
437
438 /* Newlines are CRLF, so the CR is ignored as the line isn't
439 really terminated until the LF comes. Treat a following CR
440 as end-of-headers as well.*/
441
442 if(('\r' == s->line_start[0]) ||
443 ('\n' == s->line_start[0])) {
444 /* end of response-headers from the proxy */
445 s->ptr = s->connect_buffer;
446 if((407 == k->httpcode) && !data->state.authproblem) {
447 /* If we get a 407 response code with content length
448 when we have no auth problem, we must ignore the
449 whole response-body */
450 s->keepon = 2;
451
452 if(s->cl) {
453 infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
454 " bytes of response-body\n", s->cl);
455 }
456 else if(s->chunked_encoding) {
457 CHUNKcode r;
458
459 infof(data, "Ignore chunked response-body\n");
460
461 /* We set ignorebody true here since the chunked
462 decoder function will acknowledge that. Pay
463 attention so that this is cleared again when this
464 function returns! */
465 k->ignorebody = TRUE;
466
467 if(s->line_start[1] == '\n') {
468 /* this can only be a LF if the letter at index 0
469 was a CR */
470 s->line_start++;
471 }
472
473 /* now parse the chunked piece of data so that we can
474 properly tell when the stream ends */
475 r = Curl_httpchunk_read(conn, s->line_start + 1, 1, &gotbytes);
476 if(r == CHUNKE_STOP) {
477 /* we're done reading chunks! */
478 infof(data, "chunk reading DONE\n");
479 s->keepon = FALSE;
480 /* we did the full CONNECT treatment, go to COMPLETE */
481 s->tunnel_state = TUNNEL_COMPLETE;
482 }
483 }
484 else {
485 /* without content-length or chunked encoding, we
486 can't keep the connection alive since the close is
487 the end signal so we bail out at once instead */
488 s->keepon = FALSE;
489 }
490 }
491 else
492 s->keepon = FALSE;
493 if(!s->cl)
494 /* we did the full CONNECT treatment, go to COMPLETE */
495 s->tunnel_state = TUNNEL_COMPLETE;
496 continue;
497 }
498
499 s->line_start[s->perline] = 0; /* zero terminate the buffer */
500 if((checkprefix("WWW-Authenticate:", s->line_start) &&
501 (401 == k->httpcode)) ||
502 (checkprefix("Proxy-authenticate:", s->line_start) &&
503 (407 == k->httpcode))) {
504
505 bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
506 char *auth = Curl_copy_header_value(s->line_start);
507 if(!auth)
508 return CURLE_OUT_OF_MEMORY;
509
510 result = Curl_http_input_auth(conn, proxy, auth);
511
512 free(auth);
513
514 if(result)
515 return result;
516 }
517 else if(checkprefix("Content-Length:", s->line_start)) {
518 if(k->httpcode/100 == 2) {
519 /* A client MUST ignore any Content-Length or Transfer-Encoding
520 header fields received in a successful response to CONNECT.
521 "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
522 infof(data, "Ignoring Content-Length in CONNECT %03d response\n",
523 k->httpcode);
524 }
525 else {
526 (void)curlx_strtoofft(s->line_start +
527 strlen("Content-Length:"), NULL, 10, &s->cl);
528 }
529 }
530 else if(Curl_compareheader(s->line_start, "Connection:", "close"))
531 s->close_connection = TRUE;
532 else if(checkprefix("Transfer-Encoding:", s->line_start)) {
533 if(k->httpcode/100 == 2) {
534 /* A client MUST ignore any Content-Length or Transfer-Encoding
535 header fields received in a successful response to CONNECT.
536 "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
537 infof(data, "Ignoring Transfer-Encoding in "
538 "CONNECT %03d response\n", k->httpcode);
539 }
540 else if(Curl_compareheader(s->line_start,
541 "Transfer-Encoding:", "chunked")) {
542 infof(data, "CONNECT responded chunked\n");
543 s->chunked_encoding = TRUE;
544 /* init our chunky engine */
545 Curl_httpchunk_init(conn);
546 }
547 }
548 else if(Curl_compareheader(s->line_start,
549 "Proxy-Connection:", "close"))
550 s->close_connection = TRUE;
551 else if(2 == sscanf(s->line_start, "HTTP/1.%d %d",
552 &subversion,
553 &k->httpcode)) {
554 /* store the HTTP code from the proxy */
555 data->info.httpproxycode = k->httpcode;
556 }
557
558 s->perline = 0; /* line starts over here */
559 s->ptr = s->connect_buffer;
560 s->line_start = s->ptr;
561 } /* while there's buffer left and loop is requested */
562
563 if(Curl_pgrsUpdate(conn))
564 return CURLE_ABORTED_BY_CALLBACK;
565
566 if(error)
567 return CURLE_RECV_ERROR;
568
569 if(data->info.httpproxycode/100 != 2) {
570 /* Deal with the possibly already received authenticate
571 headers. 'newurl' is set to a new URL if we must loop. */
572 result = Curl_http_auth_act(conn);
573 if(result)
574 return result;
575
576 if(conn->bits.close)
577 /* the connection has been marked for closure, most likely in the
578 Curl_http_auth_act() function and thus we can kill it at once
579 below */
580 s->close_connection = TRUE;
581 }
582
583 if(s->close_connection && data->req.newurl) {
584 /* Connection closed by server. Don't use it anymore */
585 Curl_closesocket(conn, conn->sock[sockindex]);
586 conn->sock[sockindex] = CURL_SOCKET_BAD;
587 break;
588 }
589 } /* END READING RESPONSE PHASE */
590
591 /* If we are supposed to continue and request a new URL, which basically
592 * means the HTTP authentication is still going on so if the tunnel
593 * is complete we start over in INIT state */
594 if(data->req.newurl && (TUNNEL_COMPLETE == s->tunnel_state)) {
595 connect_init(conn, TRUE); /* reinit */
596 }
597
598 } while(data->req.newurl);
599
600 if(data->info.httpproxycode/100 != 2) {
601 if(s->close_connection && data->req.newurl) {
602 conn->bits.proxy_connect_closed = TRUE;
603 infof(data, "Connect me again please\n");
604 connect_done(conn);
605 }
606 else {
607 free(data->req.newurl);
608 data->req.newurl = NULL;
609 /* failure, close this connection to avoid re-use */
610 streamclose(conn, "proxy CONNECT failure");
611 Curl_closesocket(conn, conn->sock[sockindex]);
612 conn->sock[sockindex] = CURL_SOCKET_BAD;
613 }
614
615 /* to back to init state */
616 s->tunnel_state = TUNNEL_INIT;
617
618 if(conn->bits.proxy_connect_closed)
619 /* this is not an error, just part of the connection negotiation */
620 return CURLE_OK;
621 failf(data, "Received HTTP code %d from proxy after CONNECT",
622 data->req.httpcode);
623 return CURLE_RECV_ERROR;
624 }
625
626 s->tunnel_state = TUNNEL_COMPLETE;
627
628 /* If a proxy-authorization header was used for the proxy, then we should
629 make sure that it isn't accidentally used for the document request
630 after we've connected. So let's free and clear it here. */
631 Curl_safefree(conn->allocptr.proxyuserpwd);
632 conn->allocptr.proxyuserpwd = NULL;
633
634 data->state.authproxy.done = TRUE;
635
636 infof(data, "Proxy replied %d to CONNECT request\n",
637 data->info.httpproxycode);
638 data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
639 conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the
640 document request */
641 return CURLE_OK;
642}
643
644void Curl_connect_free(struct Curl_easy *data)
645{
646 struct connectdata *conn = data->conn;
647 struct http_connect_state *s = conn->connect_state;
648 if(s) {
649 free(s);
650 conn->connect_state = NULL;
651 }
652}
653
654/*
655 * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
656 * function will issue the necessary commands to get a seamless tunnel through
657 * this proxy. After that, the socket can be used just as a normal socket.
658 */
659
660CURLcode Curl_proxyCONNECT(struct connectdata *conn,
661 int sockindex,
662 const char *hostname,
663 int remote_port)
664{
665 CURLcode result;
666 if(!conn->connect_state) {
667 result = connect_init(conn, FALSE);
668 if(result)
669 return result;
670 }
671 result = CONNECT(conn, sockindex, hostname, remote_port);
672
673 if(result || Curl_connect_complete(conn))
674 connect_done(conn);
675
676 return result;
677}
678
679#else
680void Curl_connect_free(struct Curl_easy *data)
681{
682 (void)data;
683}
684
685#endif /* CURL_DISABLE_PROXY */
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette