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