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