VirtualBox

source: vbox/trunk/src/libs/curl-8.3.0/lib/cf-h1-proxy.c@ 101259

Last change on this file since 101259 was 101259, checked in by vboxsync, 19 months ago

curl-8.3.0: fixing export flag for some files. bugref:10526

File size: 34.7 KB
Line 
1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <[email protected]>, 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.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 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24
25#include "curl_setup.h"
26
27#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
28
29#include <curl/curl.h>
30#ifdef USE_HYPER
31#include <hyper.h>
32#endif
33#include "urldata.h"
34#include "dynbuf.h"
35#include "sendf.h"
36#include "http.h"
37#include "http_proxy.h"
38#include "url.h"
39#include "select.h"
40#include "progress.h"
41#include "cfilters.h"
42#include "cf-h1-proxy.h"
43#include "connect.h"
44#include "curl_trc.h"
45#include "curlx.h"
46#include "vtls/vtls.h"
47#include "transfer.h"
48#include "multiif.h"
49
50/* The last 3 #include files should be in this order */
51#include "curl_printf.h"
52#include "curl_memory.h"
53#include "memdebug.h"
54
55
56typedef enum {
57 H1_TUNNEL_INIT, /* init/default/no tunnel state */
58 H1_TUNNEL_CONNECT, /* CONNECT request is being send */
59 H1_TUNNEL_RECEIVE, /* CONNECT answer is being received */
60 H1_TUNNEL_RESPONSE, /* CONNECT response received completely */
61 H1_TUNNEL_ESTABLISHED,
62 H1_TUNNEL_FAILED
63} h1_tunnel_state;
64
65/* struct for HTTP CONNECT tunneling */
66struct h1_tunnel_state {
67 int sockindex;
68 const char *hostname;
69 int remote_port;
70 struct HTTP CONNECT;
71 struct dynbuf rcvbuf;
72 struct dynbuf req;
73 size_t nsend;
74 size_t headerlines;
75 enum keeponval {
76 KEEPON_DONE,
77 KEEPON_CONNECT,
78 KEEPON_IGNORE
79 } keepon;
80 curl_off_t cl; /* size of content to read and ignore */
81 h1_tunnel_state tunnel_state;
82 BIT(chunked_encoding);
83 BIT(close_connection);
84};
85
86
87static bool tunnel_is_established(struct h1_tunnel_state *ts)
88{
89 return ts && (ts->tunnel_state == H1_TUNNEL_ESTABLISHED);
90}
91
92static bool tunnel_is_failed(struct h1_tunnel_state *ts)
93{
94 return ts && (ts->tunnel_state == H1_TUNNEL_FAILED);
95}
96
97static CURLcode tunnel_reinit(struct h1_tunnel_state *ts,
98 struct connectdata *conn,
99 struct Curl_easy *data)
100{
101 (void)data;
102 DEBUGASSERT(ts);
103 Curl_dyn_reset(&ts->rcvbuf);
104 Curl_dyn_reset(&ts->req);
105 ts->tunnel_state = H1_TUNNEL_INIT;
106 ts->keepon = KEEPON_CONNECT;
107 ts->cl = 0;
108 ts->close_connection = FALSE;
109
110 if(conn->bits.conn_to_host)
111 ts->hostname = conn->conn_to_host.name;
112 else if(ts->sockindex == SECONDARYSOCKET)
113 ts->hostname = conn->secondaryhostname;
114 else
115 ts->hostname = conn->host.name;
116
117 if(ts->sockindex == SECONDARYSOCKET)
118 ts->remote_port = conn->secondary_port;
119 else if(conn->bits.conn_to_port)
120 ts->remote_port = conn->conn_to_port;
121 else
122 ts->remote_port = conn->remote_port;
123
124 return CURLE_OK;
125}
126
127static CURLcode tunnel_init(struct h1_tunnel_state **pts,
128 struct Curl_easy *data,
129 struct connectdata *conn,
130 int sockindex)
131{
132 struct h1_tunnel_state *ts;
133 CURLcode result;
134
135 if(conn->handler->flags & PROTOPT_NOTCPPROXY) {
136 failf(data, "%s cannot be done over CONNECT", conn->handler->scheme);
137 return CURLE_UNSUPPORTED_PROTOCOL;
138 }
139
140 /* we might need the upload buffer for streaming a partial request */
141 result = Curl_get_upload_buffer(data);
142 if(result)
143 return result;
144
145 ts = calloc(1, sizeof(*ts));
146 if(!ts)
147 return CURLE_OUT_OF_MEMORY;
148
149 ts->sockindex = sockindex;
150 infof(data, "allocate connect buffer");
151
152 Curl_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS);
153 Curl_dyn_init(&ts->req, DYN_HTTP_REQUEST);
154
155 *pts = ts;
156 connkeep(conn, "HTTP proxy CONNECT");
157 return tunnel_reinit(ts, conn, data);
158}
159
160static void h1_tunnel_go_state(struct Curl_cfilter *cf,
161 struct h1_tunnel_state *ts,
162 h1_tunnel_state new_state,
163 struct Curl_easy *data)
164{
165 if(ts->tunnel_state == new_state)
166 return;
167 /* leaving this one */
168 switch(ts->tunnel_state) {
169 case H1_TUNNEL_CONNECT:
170 data->req.ignorebody = FALSE;
171 break;
172 default:
173 break;
174 }
175 /* entering this one */
176 switch(new_state) {
177 case H1_TUNNEL_INIT:
178 CURL_TRC_CF(data, cf, "new tunnel state 'init'");
179 tunnel_reinit(ts, cf->conn, data);
180 break;
181
182 case H1_TUNNEL_CONNECT:
183 CURL_TRC_CF(data, cf, "new tunnel state 'connect'");
184 ts->tunnel_state = H1_TUNNEL_CONNECT;
185 ts->keepon = KEEPON_CONNECT;
186 Curl_dyn_reset(&ts->rcvbuf);
187 break;
188
189 case H1_TUNNEL_RECEIVE:
190 CURL_TRC_CF(data, cf, "new tunnel state 'receive'");
191 ts->tunnel_state = H1_TUNNEL_RECEIVE;
192 break;
193
194 case H1_TUNNEL_RESPONSE:
195 CURL_TRC_CF(data, cf, "new tunnel state 'response'");
196 ts->tunnel_state = H1_TUNNEL_RESPONSE;
197 break;
198
199 case H1_TUNNEL_ESTABLISHED:
200 CURL_TRC_CF(data, cf, "new tunnel state 'established'");
201 infof(data, "CONNECT phase completed");
202 data->state.authproxy.done = TRUE;
203 data->state.authproxy.multipass = FALSE;
204 /* FALLTHROUGH */
205 case H1_TUNNEL_FAILED:
206 if(new_state == H1_TUNNEL_FAILED)
207 CURL_TRC_CF(data, cf, "new tunnel state 'failed'");
208 ts->tunnel_state = new_state;
209 Curl_dyn_reset(&ts->rcvbuf);
210 Curl_dyn_reset(&ts->req);
211 /* restore the protocol pointer */
212 data->info.httpcode = 0; /* clear it as it might've been used for the
213 proxy */
214 /* If a proxy-authorization header was used for the proxy, then we should
215 make sure that it isn't accidentally used for the document request
216 after we've connected. So let's free and clear it here. */
217 Curl_safefree(data->state.aptr.proxyuserpwd);
218#ifdef USE_HYPER
219 data->state.hconnect = FALSE;
220#endif
221 break;
222 }
223}
224
225static void tunnel_free(struct Curl_cfilter *cf,
226 struct Curl_easy *data)
227{
228 struct h1_tunnel_state *ts = cf->ctx;
229 if(ts) {
230 h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
231 Curl_dyn_free(&ts->rcvbuf);
232 Curl_dyn_free(&ts->req);
233 free(ts);
234 cf->ctx = NULL;
235 }
236}
237
238static CURLcode CONNECT_host(struct Curl_easy *data,
239 struct connectdata *conn,
240 const char *hostname,
241 int remote_port,
242 char **connecthostp,
243 char **hostp)
244{
245 char *hostheader; /* for CONNECT */
246 char *host = NULL; /* Host: */
247 bool ipv6_ip = conn->bits.ipv6_ip;
248
249 /* the hostname may be different */
250 if(hostname != conn->host.name)
251 ipv6_ip = (strchr(hostname, ':') != NULL);
252 hostheader = /* host:port with IPv6 support */
253 aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
254 remote_port);
255 if(!hostheader)
256 return CURLE_OUT_OF_MEMORY;
257
258 if(!Curl_checkProxyheaders(data, conn, STRCONST("Host"))) {
259 host = aprintf("Host: %s\r\n", hostheader);
260 if(!host) {
261 free(hostheader);
262 return CURLE_OUT_OF_MEMORY;
263 }
264 }
265 *connecthostp = hostheader;
266 *hostp = host;
267 return CURLE_OK;
268}
269
270#ifndef USE_HYPER
271static CURLcode start_CONNECT(struct Curl_cfilter *cf,
272 struct Curl_easy *data,
273 struct h1_tunnel_state *ts)
274{
275 struct connectdata *conn = cf->conn;
276 char *hostheader = NULL;
277 char *host = NULL;
278 const char *httpv;
279 CURLcode result;
280
281 infof(data, "Establish HTTP proxy tunnel to %s:%d",
282 ts->hostname, ts->remote_port);
283
284 /* This only happens if we've looped here due to authentication
285 reasons, and we don't really use the newly cloned URL here
286 then. Just free() it. */
287 Curl_safefree(data->req.newurl);
288
289 result = CONNECT_host(data, conn,
290 ts->hostname, ts->remote_port,
291 &hostheader, &host);
292 if(result)
293 goto out;
294
295 /* Setup the proxy-authorization header, if any */
296 result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET,
297 hostheader, TRUE);
298 if(result)
299 goto out;
300
301 httpv = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? "1.0" : "1.1";
302
303 result =
304 Curl_dyn_addf(&ts->req,
305 "CONNECT %s HTTP/%s\r\n"
306 "%s" /* Host: */
307 "%s", /* Proxy-Authorization */
308 hostheader,
309 httpv,
310 host?host:"",
311 data->state.aptr.proxyuserpwd?
312 data->state.aptr.proxyuserpwd:"");
313 if(result)
314 goto out;
315
316 if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent"))
317 && data->set.str[STRING_USERAGENT])
318 result = Curl_dyn_addf(&ts->req, "User-Agent: %s\r\n",
319 data->set.str[STRING_USERAGENT]);
320 if(result)
321 goto out;
322
323 if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection")))
324 result = Curl_dyn_addn(&ts->req,
325 STRCONST("Proxy-Connection: Keep-Alive\r\n"));
326 if(result)
327 goto out;
328
329 result = Curl_add_custom_headers(data, TRUE, &ts->req);
330 if(result)
331 goto out;
332
333 /* CRLF terminate the request */
334 result = Curl_dyn_addn(&ts->req, STRCONST("\r\n"));
335 if(result)
336 goto out;
337
338 /* Send the connect request to the proxy */
339 result = Curl_buffer_send(&ts->req, data, &ts->CONNECT,
340 &data->info.request_size, 0,
341 ts->sockindex);
342 ts->headerlines = 0;
343
344out:
345 if(result)
346 failf(data, "Failed sending CONNECT to proxy");
347 free(host);
348 free(hostheader);
349 return result;
350}
351
352static CURLcode send_CONNECT(struct Curl_easy *data,
353 struct connectdata *conn,
354 struct h1_tunnel_state *ts,
355 bool *done)
356{
357 struct SingleRequest *k = &data->req;
358 struct HTTP *http = &ts->CONNECT;
359 CURLcode result = CURLE_OK;
360
361 if(http->sending != HTTPSEND_REQUEST)
362 goto out;
363
364 if(!ts->nsend) {
365 size_t fillcount;
366 k->upload_fromhere = data->state.ulbuf;
367 result = Curl_fillreadbuffer(data, data->set.upload_buffer_size,
368 &fillcount);
369 if(result)
370 goto out;
371 ts->nsend = fillcount;
372 }
373 if(ts->nsend) {
374 ssize_t bytes_written;
375 /* write to socket (send away data) */
376 result = Curl_write(data,
377 conn->writesockfd, /* socket to send to */
378 k->upload_fromhere, /* buffer pointer */
379 ts->nsend, /* buffer size */
380 &bytes_written); /* actually sent */
381 if(result)
382 goto out;
383 /* send to debug callback! */
384 Curl_debug(data, CURLINFO_HEADER_OUT,
385 k->upload_fromhere, bytes_written);
386
387 ts->nsend -= bytes_written;
388 k->upload_fromhere += bytes_written;
389 }
390 if(!ts->nsend)
391 http->sending = HTTPSEND_NADA;
392
393out:
394 if(result)
395 failf(data, "Failed sending CONNECT to proxy");
396 *done = (http->sending != HTTPSEND_REQUEST);
397 return result;
398}
399
400static CURLcode on_resp_header(struct Curl_cfilter *cf,
401 struct Curl_easy *data,
402 struct h1_tunnel_state *ts,
403 const char *header)
404{
405 CURLcode result = CURLE_OK;
406 struct SingleRequest *k = &data->req;
407 (void)cf;
408
409 if((checkprefix("WWW-Authenticate:", header) &&
410 (401 == k->httpcode)) ||
411 (checkprefix("Proxy-authenticate:", header) &&
412 (407 == k->httpcode))) {
413
414 bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
415 char *auth = Curl_copy_header_value(header);
416 if(!auth)
417 return CURLE_OUT_OF_MEMORY;
418
419 CURL_TRC_CF(data, cf, "CONNECT: fwd auth header '%s'", header);
420 result = Curl_http_input_auth(data, proxy, auth);
421
422 free(auth);
423
424 if(result)
425 return result;
426 }
427 else if(checkprefix("Content-Length:", header)) {
428 if(k->httpcode/100 == 2) {
429 /* A client MUST ignore any Content-Length or Transfer-Encoding
430 header fields received in a successful response to CONNECT.
431 "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
432 infof(data, "Ignoring Content-Length in CONNECT %03d response",
433 k->httpcode);
434 }
435 else {
436 (void)curlx_strtoofft(header + strlen("Content-Length:"),
437 NULL, 10, &ts->cl);
438 }
439 }
440 else if(Curl_compareheader(header,
441 STRCONST("Connection:"), STRCONST("close")))
442 ts->close_connection = TRUE;
443 else if(checkprefix("Transfer-Encoding:", header)) {
444 if(k->httpcode/100 == 2) {
445 /* A client MUST ignore any Content-Length or Transfer-Encoding
446 header fields received in a successful response to CONNECT.
447 "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
448 infof(data, "Ignoring Transfer-Encoding in "
449 "CONNECT %03d response", k->httpcode);
450 }
451 else if(Curl_compareheader(header,
452 STRCONST("Transfer-Encoding:"),
453 STRCONST("chunked"))) {
454 infof(data, "CONNECT responded chunked");
455 ts->chunked_encoding = TRUE;
456 /* init our chunky engine */
457 Curl_httpchunk_init(data);
458 }
459 }
460 else if(Curl_compareheader(header,
461 STRCONST("Proxy-Connection:"),
462 STRCONST("close")))
463 ts->close_connection = TRUE;
464 else if(!strncmp(header, "HTTP/1.", 7) &&
465 ((header[7] == '0') || (header[7] == '1')) &&
466 (header[8] == ' ') &&
467 ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) &&
468 !ISDIGIT(header[12])) {
469 /* store the HTTP code from the proxy */
470 data->info.httpproxycode = k->httpcode = (header[9] - '0') * 100 +
471 (header[10] - '0') * 10 + (header[11] - '0');
472 }
473 return result;
474}
475
476static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
477 struct Curl_easy *data,
478 struct h1_tunnel_state *ts,
479 bool *done)
480{
481 CURLcode result = CURLE_OK;
482 struct SingleRequest *k = &data->req;
483 curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data);
484 char *linep;
485 size_t perline;
486 int error;
487
488#define SELECT_OK 0
489#define SELECT_ERROR 1
490
491 error = SELECT_OK;
492 *done = FALSE;
493
494 if(!Curl_conn_data_pending(data, ts->sockindex))
495 return CURLE_OK;
496
497 while(ts->keepon) {
498 ssize_t gotbytes;
499 char byte;
500
501 /* Read one byte at a time to avoid a race condition. Wait at most one
502 second before looping to ensure continuous pgrsUpdates. */
503 result = Curl_read(data, tunnelsocket, &byte, 1, &gotbytes);
504 if(result == CURLE_AGAIN)
505 /* socket buffer drained, return */
506 return CURLE_OK;
507
508 if(Curl_pgrsUpdate(data))
509 return CURLE_ABORTED_BY_CALLBACK;
510
511 if(result) {
512 ts->keepon = KEEPON_DONE;
513 break;
514 }
515
516 if(gotbytes <= 0) {
517 if(data->set.proxyauth && data->state.authproxy.avail &&
518 data->state.aptr.proxyuserpwd) {
519 /* proxy auth was requested and there was proxy auth available,
520 then deem this as "mere" proxy disconnect */
521 ts->close_connection = TRUE;
522 infof(data, "Proxy CONNECT connection closed");
523 }
524 else {
525 error = SELECT_ERROR;
526 failf(data, "Proxy CONNECT aborted");
527 }
528 ts->keepon = KEEPON_DONE;
529 break;
530 }
531
532 if(ts->keepon == KEEPON_IGNORE) {
533 /* This means we are currently ignoring a response-body */
534
535 if(ts->cl) {
536 /* A Content-Length based body: simply count down the counter
537 and make sure to break out of the loop when we're done! */
538 ts->cl--;
539 if(ts->cl <= 0) {
540 ts->keepon = KEEPON_DONE;
541 break;
542 }
543 }
544 else {
545 /* chunked-encoded body, so we need to do the chunked dance
546 properly to know when the end of the body is reached */
547 CHUNKcode r;
548 CURLcode extra;
549 ssize_t tookcareof = 0;
550
551 /* now parse the chunked piece of data so that we can
552 properly tell when the stream ends */
553 r = Curl_httpchunk_read(data, &byte, 1, &tookcareof, &extra);
554 if(r == CHUNKE_STOP) {
555 /* we're done reading chunks! */
556 infof(data, "chunk reading DONE");
557 ts->keepon = KEEPON_DONE;
558 }
559 }
560 continue;
561 }
562
563 if(Curl_dyn_addn(&ts->rcvbuf, &byte, 1)) {
564 failf(data, "CONNECT response too large");
565 return CURLE_RECV_ERROR;
566 }
567
568 /* if this is not the end of a header line then continue */
569 if(byte != 0x0a)
570 continue;
571
572 ts->headerlines++;
573 linep = Curl_dyn_ptr(&ts->rcvbuf);
574 perline = Curl_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */
575
576 /* output debug if that is requested */
577 Curl_debug(data, CURLINFO_HEADER_IN, linep, perline);
578
579 if(!data->set.suppress_connect_headers) {
580 /* send the header to the callback */
581 int writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT |
582 (data->set.include_header ? CLIENTWRITE_BODY : 0) |
583 (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0);
584
585 result = Curl_client_write(data, writetype, linep, perline);
586 if(result)
587 return result;
588 }
589
590 result = Curl_bump_headersize(data, perline, TRUE);
591 if(result)
592 return result;
593
594 /* Newlines are CRLF, so the CR is ignored as the line isn't
595 really terminated until the LF comes. Treat a following CR
596 as end-of-headers as well.*/
597
598 if(('\r' == linep[0]) ||
599 ('\n' == linep[0])) {
600 /* end of response-headers from the proxy */
601
602 if((407 == k->httpcode) && !data->state.authproblem) {
603 /* If we get a 407 response code with content length
604 when we have no auth problem, we must ignore the
605 whole response-body */
606 ts->keepon = KEEPON_IGNORE;
607
608 if(ts->cl) {
609 infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
610 " bytes of response-body", ts->cl);
611 }
612 else if(ts->chunked_encoding) {
613 CHUNKcode r;
614 CURLcode extra;
615
616 infof(data, "Ignore chunked response-body");
617
618 /* We set ignorebody true here since the chunked decoder
619 function will acknowledge that. Pay attention so that this is
620 cleared again when this function returns! */
621 k->ignorebody = TRUE;
622
623 if(linep[1] == '\n')
624 /* this can only be a LF if the letter at index 0 was a CR */
625 linep++;
626
627 /* now parse the chunked piece of data so that we can properly
628 tell when the stream ends */
629 r = Curl_httpchunk_read(data, linep + 1, 1, &gotbytes,
630 &extra);
631 if(r == CHUNKE_STOP) {
632 /* we're done reading chunks! */
633 infof(data, "chunk reading DONE");
634 ts->keepon = KEEPON_DONE;
635 }
636 }
637 else {
638 /* without content-length or chunked encoding, we
639 can't keep the connection alive since the close is
640 the end signal so we bail out at once instead */
641 CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked");
642 ts->keepon = KEEPON_DONE;
643 }
644 }
645 else {
646 ts->keepon = KEEPON_DONE;
647 }
648
649 DEBUGASSERT(ts->keepon == KEEPON_IGNORE
650 || ts->keepon == KEEPON_DONE);
651 continue;
652 }
653
654 result = on_resp_header(cf, data, ts, linep);
655 if(result)
656 return result;
657
658 Curl_dyn_reset(&ts->rcvbuf);
659 } /* while there's buffer left and loop is requested */
660
661 if(error)
662 result = CURLE_RECV_ERROR;
663 *done = (ts->keepon == KEEPON_DONE);
664 if(!result && *done && data->info.httpproxycode/100 != 2) {
665 /* Deal with the possibly already received authenticate
666 headers. 'newurl' is set to a new URL if we must loop. */
667 result = Curl_http_auth_act(data);
668 }
669 return result;
670}
671
672#else /* USE_HYPER */
673/* The Hyper version of CONNECT */
674static CURLcode start_CONNECT(struct Curl_cfilter *cf,
675 struct Curl_easy *data,
676 struct h1_tunnel_state *ts)
677{
678 struct connectdata *conn = cf->conn;
679 struct hyptransfer *h = &data->hyp;
680 curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data);
681 hyper_io *io = NULL;
682 hyper_request *req = NULL;
683 hyper_headers *headers = NULL;
684 hyper_clientconn_options *options = NULL;
685 hyper_task *handshake = NULL;
686 hyper_task *task = NULL; /* for the handshake */
687 hyper_clientconn *client = NULL;
688 hyper_task *sendtask = NULL; /* for the send */
689 char *hostheader = NULL; /* for CONNECT */
690 char *host = NULL; /* Host: */
691 CURLcode result = CURLE_OUT_OF_MEMORY;
692
693 io = hyper_io_new();
694 if(!io) {
695 failf(data, "Couldn't create hyper IO");
696 result = CURLE_OUT_OF_MEMORY;
697 goto error;
698 }
699 /* tell Hyper how to read/write network data */
700 hyper_io_set_userdata(io, data);
701 hyper_io_set_read(io, Curl_hyper_recv);
702 hyper_io_set_write(io, Curl_hyper_send);
703 conn->sockfd = tunnelsocket;
704
705 data->state.hconnect = TRUE;
706
707 /* create an executor to poll futures */
708 if(!h->exec) {
709 h->exec = hyper_executor_new();
710 if(!h->exec) {
711 failf(data, "Couldn't create hyper executor");
712 result = CURLE_OUT_OF_MEMORY;
713 goto error;
714 }
715 }
716
717 options = hyper_clientconn_options_new();
718 if(!options) {
719 failf(data, "Couldn't create hyper client options");
720 result = CURLE_OUT_OF_MEMORY;
721 goto error;
722 }
723 hyper_clientconn_options_set_preserve_header_case(options, 1);
724 hyper_clientconn_options_set_preserve_header_order(options, 1);
725
726 hyper_clientconn_options_exec(options, h->exec);
727
728 /* "Both the `io` and the `options` are consumed in this function
729 call" */
730 handshake = hyper_clientconn_handshake(io, options);
731 if(!handshake) {
732 failf(data, "Couldn't create hyper client handshake");
733 result = CURLE_OUT_OF_MEMORY;
734 goto error;
735 }
736 io = NULL;
737 options = NULL;
738
739 if(HYPERE_OK != hyper_executor_push(h->exec, handshake)) {
740 failf(data, "Couldn't hyper_executor_push the handshake");
741 result = CURLE_OUT_OF_MEMORY;
742 goto error;
743 }
744 handshake = NULL; /* ownership passed on */
745
746 task = hyper_executor_poll(h->exec);
747 if(!task) {
748 failf(data, "Couldn't hyper_executor_poll the handshake");
749 result = CURLE_OUT_OF_MEMORY;
750 goto error;
751 }
752
753 client = hyper_task_value(task);
754 hyper_task_free(task);
755
756 req = hyper_request_new();
757 if(!req) {
758 failf(data, "Couldn't hyper_request_new");
759 result = CURLE_OUT_OF_MEMORY;
760 goto error;
761 }
762 if(hyper_request_set_method(req, (uint8_t *)"CONNECT",
763 strlen("CONNECT"))) {
764 failf(data, "error setting method");
765 result = CURLE_OUT_OF_MEMORY;
766 goto error;
767 }
768
769 infof(data, "Establish HTTP proxy tunnel to %s:%d",
770 ts->hostname, ts->remote_port);
771
772 /* This only happens if we've looped here due to authentication
773 reasons, and we don't really use the newly cloned URL here
774 then. Just free() it. */
775 Curl_safefree(data->req.newurl);
776
777 result = CONNECT_host(data, conn, ts->hostname, ts->remote_port,
778 &hostheader, &host);
779 if(result)
780 goto error;
781
782 if(hyper_request_set_uri(req, (uint8_t *)hostheader,
783 strlen(hostheader))) {
784 failf(data, "error setting path");
785 result = CURLE_OUT_OF_MEMORY;
786 goto error;
787 }
788 if(data->set.verbose) {
789 char *se = aprintf("CONNECT %s HTTP/1.1\r\n", hostheader);
790 if(!se) {
791 result = CURLE_OUT_OF_MEMORY;
792 goto error;
793 }
794 Curl_debug(data, CURLINFO_HEADER_OUT, se, strlen(se));
795 free(se);
796 }
797 /* Setup the proxy-authorization header, if any */
798 result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET,
799 hostheader, TRUE);
800 if(result)
801 goto error;
802 Curl_safefree(hostheader);
803
804 /* default is 1.1 */
805 if((conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) &&
806 (HYPERE_OK != hyper_request_set_version(req,
807 HYPER_HTTP_VERSION_1_0))) {
808 failf(data, "error setting HTTP version");
809 result = CURLE_OUT_OF_MEMORY;
810 goto error;
811 }
812
813 headers = hyper_request_headers(req);
814 if(!headers) {
815 failf(data, "hyper_request_headers");
816 result = CURLE_OUT_OF_MEMORY;
817 goto error;
818 }
819 if(host) {
820 result = Curl_hyper_header(data, headers, host);
821 if(result)
822 goto error;
823 Curl_safefree(host);
824 }
825
826 if(data->state.aptr.proxyuserpwd) {
827 result = Curl_hyper_header(data, headers,
828 data->state.aptr.proxyuserpwd);
829 if(result)
830 goto error;
831 }
832
833 if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent")) &&
834 data->set.str[STRING_USERAGENT]) {
835 struct dynbuf ua;
836 Curl_dyn_init(&ua, DYN_HTTP_REQUEST);
837 result = Curl_dyn_addf(&ua, "User-Agent: %s\r\n",
838 data->set.str[STRING_USERAGENT]);
839 if(result)
840 goto error;
841 result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&ua));
842 if(result)
843 goto error;
844 Curl_dyn_free(&ua);
845 }
846
847 if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection"))) {
848 result = Curl_hyper_header(data, headers,
849 "Proxy-Connection: Keep-Alive");
850 if(result)
851 goto error;
852 }
853
854 result = Curl_add_custom_headers(data, TRUE, headers);
855 if(result)
856 goto error;
857
858 sendtask = hyper_clientconn_send(client, req);
859 if(!sendtask) {
860 failf(data, "hyper_clientconn_send");
861 result = CURLE_OUT_OF_MEMORY;
862 goto error;
863 }
864 req = NULL;
865
866 if(HYPERE_OK != hyper_executor_push(h->exec, sendtask)) {
867 failf(data, "Couldn't hyper_executor_push the send");
868 result = CURLE_OUT_OF_MEMORY;
869 goto error;
870 }
871 sendtask = NULL; /* ownership passed on */
872
873 hyper_clientconn_free(client);
874 client = NULL;
875
876error:
877 free(host);
878 free(hostheader);
879 if(io)
880 hyper_io_free(io);
881 if(options)
882 hyper_clientconn_options_free(options);
883 if(handshake)
884 hyper_task_free(handshake);
885 if(client)
886 hyper_clientconn_free(client);
887 if(req)
888 hyper_request_free(req);
889
890 return result;
891}
892
893static CURLcode send_CONNECT(struct Curl_easy *data,
894 struct connectdata *conn,
895 struct h1_tunnel_state *ts,
896 bool *done)
897{
898 struct hyptransfer *h = &data->hyp;
899 hyper_task *task = NULL;
900 hyper_error *hypererr = NULL;
901 CURLcode result = CURLE_OK;
902
903 (void)ts;
904 (void)conn;
905 do {
906 task = hyper_executor_poll(h->exec);
907 if(task) {
908 bool error = hyper_task_type(task) == HYPER_TASK_ERROR;
909 if(error)
910 hypererr = hyper_task_value(task);
911 hyper_task_free(task);
912 if(error) {
913 /* this could probably use a better error code? */
914 result = CURLE_OUT_OF_MEMORY;
915 goto error;
916 }
917 }
918 } while(task);
919error:
920 *done = (result == CURLE_OK);
921 if(hypererr) {
922 uint8_t errbuf[256];
923 size_t errlen = hyper_error_print(hypererr, errbuf, sizeof(errbuf));
924 failf(data, "Hyper: %.*s", (int)errlen, errbuf);
925 hyper_error_free(hypererr);
926 }
927 return result;
928}
929
930static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
931 struct Curl_easy *data,
932 struct h1_tunnel_state *ts,
933 bool *done)
934{
935 struct hyptransfer *h = &data->hyp;
936 CURLcode result;
937 int didwhat;
938
939 (void)ts;
940 *done = FALSE;
941 result = Curl_hyper_stream(data, cf->conn, &didwhat, done,
942 CURL_CSELECT_IN | CURL_CSELECT_OUT);
943 if(result || !*done)
944 return result;
945 if(h->exec) {
946 hyper_executor_free(h->exec);
947 h->exec = NULL;
948 }
949 if(h->read_waker) {
950 hyper_waker_free(h->read_waker);
951 h->read_waker = NULL;
952 }
953 if(h->write_waker) {
954 hyper_waker_free(h->write_waker);
955 h->write_waker = NULL;
956 }
957 return result;
958}
959
960#endif /* USE_HYPER */
961
962static CURLcode H1_CONNECT(struct Curl_cfilter *cf,
963 struct Curl_easy *data,
964 struct h1_tunnel_state *ts)
965{
966 struct connectdata *conn = cf->conn;
967 CURLcode result;
968 bool done;
969
970 if(tunnel_is_established(ts))
971 return CURLE_OK;
972 if(tunnel_is_failed(ts))
973 return CURLE_RECV_ERROR; /* Need a cfilter close and new bootstrap */
974
975 do {
976 timediff_t check;
977
978 check = Curl_timeleft(data, NULL, TRUE);
979 if(check <= 0) {
980 failf(data, "Proxy CONNECT aborted due to timeout");
981 result = CURLE_OPERATION_TIMEDOUT;
982 goto out;
983 }
984
985 switch(ts->tunnel_state) {
986 case H1_TUNNEL_INIT:
987 /* Prepare the CONNECT request and make a first attempt to send. */
988 CURL_TRC_CF(data, cf, "CONNECT start");
989 result = start_CONNECT(cf, data, ts);
990 if(result)
991 goto out;
992 h1_tunnel_go_state(cf, ts, H1_TUNNEL_CONNECT, data);
993 /* FALLTHROUGH */
994
995 case H1_TUNNEL_CONNECT:
996 /* see that the request is completely sent */
997 CURL_TRC_CF(data, cf, "CONNECT send");
998 result = send_CONNECT(data, cf->conn, ts, &done);
999 if(result || !done)
1000 goto out;
1001 h1_tunnel_go_state(cf, ts, H1_TUNNEL_RECEIVE, data);
1002 /* FALLTHROUGH */
1003
1004 case H1_TUNNEL_RECEIVE:
1005 /* read what is there */
1006 CURL_TRC_CF(data, cf, "CONNECT receive");
1007 result = recv_CONNECT_resp(cf, data, ts, &done);
1008 if(Curl_pgrsUpdate(data)) {
1009 result = CURLE_ABORTED_BY_CALLBACK;
1010 goto out;
1011 }
1012 /* error or not complete yet. return for more multi-multi */
1013 if(result || !done)
1014 goto out;
1015 /* got it */
1016 h1_tunnel_go_state(cf, ts, H1_TUNNEL_RESPONSE, data);
1017 /* FALLTHROUGH */
1018
1019 case H1_TUNNEL_RESPONSE:
1020 CURL_TRC_CF(data, cf, "CONNECT response");
1021 if(data->req.newurl) {
1022 /* not the "final" response, we need to do a follow up request.
1023 * If the other side indicated a connection close, or if someone
1024 * else told us to close this connection, do so now.
1025 */
1026 if(ts->close_connection || conn->bits.close) {
1027 /* Close this filter and the sub-chain, re-connect the
1028 * sub-chain and continue. Closing this filter will
1029 * reset our tunnel state. To avoid recursion, we return
1030 * and expect to be called again.
1031 */
1032 CURL_TRC_CF(data, cf, "CONNECT need to close+open");
1033 infof(data, "Connect me again please");
1034 Curl_conn_cf_close(cf, data);
1035 connkeep(conn, "HTTP proxy CONNECT");
1036 result = Curl_conn_cf_connect(cf->next, data, FALSE, &done);
1037 goto out;
1038 }
1039 else {
1040 /* staying on this connection, reset state */
1041 h1_tunnel_go_state(cf, ts, H1_TUNNEL_INIT, data);
1042 }
1043 }
1044 break;
1045
1046 default:
1047 break;
1048 }
1049
1050 } while(data->req.newurl);
1051
1052 DEBUGASSERT(ts->tunnel_state == H1_TUNNEL_RESPONSE);
1053 if(data->info.httpproxycode/100 != 2) {
1054 /* a non-2xx response and we have no next url to try. */
1055 Curl_safefree(data->req.newurl);
1056 /* failure, close this connection to avoid reuse */
1057 streamclose(conn, "proxy CONNECT failure");
1058 h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
1059 failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode);
1060 return CURLE_RECV_ERROR;
1061 }
1062 /* 2xx response, SUCCESS! */
1063 h1_tunnel_go_state(cf, ts, H1_TUNNEL_ESTABLISHED, data);
1064 infof(data, "CONNECT tunnel established, response %d",
1065 data->info.httpproxycode);
1066 result = CURLE_OK;
1067
1068out:
1069 if(result)
1070 h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
1071 return result;
1072}
1073
1074static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf,
1075 struct Curl_easy *data,
1076 bool blocking, bool *done)
1077{
1078 CURLcode result;
1079 struct h1_tunnel_state *ts = cf->ctx;
1080
1081 if(cf->connected) {
1082 *done = TRUE;
1083 return CURLE_OK;
1084 }
1085
1086 CURL_TRC_CF(data, cf, "connect");
1087 result = cf->next->cft->do_connect(cf->next, data, blocking, done);
1088 if(result || !*done)
1089 return result;
1090
1091 *done = FALSE;
1092 if(!ts) {
1093 result = tunnel_init(&ts, data, cf->conn, cf->sockindex);
1094 if(result)
1095 return result;
1096 cf->ctx = ts;
1097 }
1098
1099 /* TODO: can we do blocking? */
1100 /* We want "seamless" operations through HTTP proxy tunnel */
1101
1102 result = H1_CONNECT(cf, data, ts);
1103 if(result)
1104 goto out;
1105 Curl_safefree(data->state.aptr.proxyuserpwd);
1106
1107out:
1108 *done = (result == CURLE_OK) && tunnel_is_established(cf->ctx);
1109 if(*done) {
1110 cf->connected = TRUE;
1111 tunnel_free(cf, data);
1112 }
1113 return result;
1114}
1115
1116static int cf_h1_proxy_get_select_socks(struct Curl_cfilter *cf,
1117 struct Curl_easy *data,
1118 curl_socket_t *socks)
1119{
1120 struct h1_tunnel_state *ts = cf->ctx;
1121 int fds;
1122
1123 fds = cf->next->cft->get_select_socks(cf->next, data, socks);
1124 if(!fds && cf->next->connected && !cf->connected) {
1125 /* If we are not connected, but the filter "below" is
1126 * and not waiting on something, we are tunneling. */
1127 socks[0] = Curl_conn_cf_get_socket(cf, data);
1128 if(ts) {
1129 /* when we've sent a CONNECT to a proxy, we should rather either
1130 wait for the socket to become readable to be able to get the
1131 response headers or if we're still sending the request, wait
1132 for write. */
1133 if(ts->CONNECT.sending == HTTPSEND_REQUEST) {
1134 return GETSOCK_WRITESOCK(0);
1135 }
1136 return GETSOCK_READSOCK(0);
1137 }
1138 return GETSOCK_WRITESOCK(0);
1139 }
1140 return fds;
1141}
1142
1143static void cf_h1_proxy_destroy(struct Curl_cfilter *cf,
1144 struct Curl_easy *data)
1145{
1146 CURL_TRC_CF(data, cf, "destroy");
1147 tunnel_free(cf, data);
1148}
1149
1150static void cf_h1_proxy_close(struct Curl_cfilter *cf,
1151 struct Curl_easy *data)
1152{
1153 CURL_TRC_CF(data, cf, "close");
1154 cf->connected = FALSE;
1155 if(cf->ctx) {
1156 h1_tunnel_go_state(cf, cf->ctx, H1_TUNNEL_INIT, data);
1157 }
1158 if(cf->next)
1159 cf->next->cft->do_close(cf->next, data);
1160}
1161
1162
1163struct Curl_cftype Curl_cft_h1_proxy = {
1164 "H1-PROXY",
1165 CF_TYPE_IP_CONNECT,
1166 0,
1167 cf_h1_proxy_destroy,
1168 cf_h1_proxy_connect,
1169 cf_h1_proxy_close,
1170 Curl_cf_http_proxy_get_host,
1171 cf_h1_proxy_get_select_socks,
1172 Curl_cf_def_data_pending,
1173 Curl_cf_def_send,
1174 Curl_cf_def_recv,
1175 Curl_cf_def_cntrl,
1176 Curl_cf_def_conn_is_alive,
1177 Curl_cf_def_conn_keep_alive,
1178 Curl_cf_def_query,
1179};
1180
1181CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at,
1182 struct Curl_easy *data)
1183{
1184 struct Curl_cfilter *cf;
1185 CURLcode result;
1186
1187 (void)data;
1188 result = Curl_cf_create(&cf, &Curl_cft_h1_proxy, NULL);
1189 if(!result)
1190 Curl_conn_cf_insert_after(cf_at, cf);
1191 return result;
1192}
1193
1194#endif /* !CURL_DISABLE_PROXY && ! CURL_DISABLE_HTTP */
Note: See TracBrowser for help on using the repository browser.

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