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