Coverage Report

Created: 2024-02-25 06:14

/src/PROJ/curl/lib/cf-h1-proxy.c
Line
Count
Source (jump to first uncovered line)
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
86
static bool tunnel_is_established(struct h1_tunnel_state *ts)
87
0
{
88
0
  return ts && (ts->tunnel_state == H1_TUNNEL_ESTABLISHED);
89
0
}
90
91
static bool tunnel_is_failed(struct h1_tunnel_state *ts)
92
0
{
93
0
  return ts && (ts->tunnel_state == H1_TUNNEL_FAILED);
94
0
}
95
96
static CURLcode tunnel_reinit(struct Curl_cfilter *cf,
97
                              struct Curl_easy *data,
98
                              struct h1_tunnel_state *ts)
99
0
{
100
0
  (void)data;
101
0
  (void)cf;
102
0
  DEBUGASSERT(ts);
103
0
  Curl_dyn_reset(&ts->rcvbuf);
104
0
  Curl_dyn_reset(&ts->request_data);
105
0
  ts->tunnel_state = H1_TUNNEL_INIT;
106
0
  ts->keepon = KEEPON_CONNECT;
107
0
  ts->cl = 0;
108
0
  ts->close_connection = FALSE;
109
0
  return CURLE_OK;
110
0
}
111
112
static CURLcode tunnel_init(struct Curl_cfilter *cf,
113
                            struct Curl_easy *data,
114
                            struct h1_tunnel_state **pts)
115
0
{
116
0
  struct h1_tunnel_state *ts;
117
0
  CURLcode result;
118
119
0
  if(cf->conn->handler->flags & PROTOPT_NOTCPPROXY) {
120
0
    failf(data, "%s cannot be done over CONNECT", cf->conn->handler->scheme);
121
0
    return CURLE_UNSUPPORTED_PROTOCOL;
122
0
  }
123
124
  /* we might need the upload buffer for streaming a partial request */
125
0
  result = Curl_get_upload_buffer(data);
126
0
  if(result)
127
0
    return result;
128
129
0
  ts = calloc(1, sizeof(*ts));
130
0
  if(!ts)
131
0
    return CURLE_OUT_OF_MEMORY;
132
133
0
  infof(data, "allocate connect buffer");
134
135
0
  Curl_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS);
136
0
  Curl_dyn_init(&ts->request_data, DYN_HTTP_REQUEST);
137
0
  Curl_httpchunk_init(data, &ts->ch, TRUE);
138
139
0
  *pts =  ts;
140
0
  connkeep(cf->conn, "HTTP proxy CONNECT");
141
0
  return tunnel_reinit(cf, data, ts);
142
0
}
143
144
static 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
0
{
149
0
  if(ts->tunnel_state == new_state)
150
0
    return;
151
  /* entering this one */
152
0
  switch(new_state) {
153
0
  case H1_TUNNEL_INIT:
154
0
    CURL_TRC_CF(data, cf, "new tunnel state 'init'");
155
0
    tunnel_reinit(cf, data, ts);
156
0
    break;
157
158
0
  case H1_TUNNEL_CONNECT:
159
0
    CURL_TRC_CF(data, cf, "new tunnel state 'connect'");
160
0
    ts->tunnel_state = H1_TUNNEL_CONNECT;
161
0
    ts->keepon = KEEPON_CONNECT;
162
0
    Curl_dyn_reset(&ts->rcvbuf);
163
0
    break;
164
165
0
  case H1_TUNNEL_RECEIVE:
166
0
    CURL_TRC_CF(data, cf, "new tunnel state 'receive'");
167
0
    ts->tunnel_state = H1_TUNNEL_RECEIVE;
168
0
    break;
169
170
0
  case H1_TUNNEL_RESPONSE:
171
0
    CURL_TRC_CF(data, cf, "new tunnel state 'response'");
172
0
    ts->tunnel_state = H1_TUNNEL_RESPONSE;
173
0
    break;
174
175
0
  case H1_TUNNEL_ESTABLISHED:
176
0
    CURL_TRC_CF(data, cf, "new tunnel state 'established'");
177
0
    infof(data, "CONNECT phase completed");
178
0
    data->state.authproxy.done = TRUE;
179
0
    data->state.authproxy.multipass = FALSE;
180
0
    FALLTHROUGH();
181
0
  case H1_TUNNEL_FAILED:
182
0
    if(new_state == H1_TUNNEL_FAILED)
183
0
      CURL_TRC_CF(data, cf, "new tunnel state 'failed'");
184
0
    ts->tunnel_state = new_state;
185
0
    Curl_dyn_reset(&ts->rcvbuf);
186
0
    Curl_dyn_reset(&ts->request_data);
187
    /* restore the protocol pointer */
188
0
    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
0
    Curl_safefree(data->state.aptr.proxyuserpwd);
194
#ifdef USE_HYPER
195
    data->state.hconnect = FALSE;
196
#endif
197
0
    break;
198
0
  }
199
0
}
200
201
static void tunnel_free(struct Curl_cfilter *cf,
202
                        struct Curl_easy *data)
203
0
{
204
0
  struct h1_tunnel_state *ts = cf->ctx;
205
0
  if(ts) {
206
0
    h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
207
0
    Curl_dyn_free(&ts->rcvbuf);
208
0
    Curl_dyn_free(&ts->request_data);
209
0
    Curl_httpchunk_free(data, &ts->ch);
210
0
    free(ts);
211
0
    cf->ctx = NULL;
212
0
  }
213
0
}
214
215
#ifndef USE_HYPER
216
static CURLcode start_CONNECT(struct Curl_cfilter *cf,
217
                              struct Curl_easy *data,
218
                              struct h1_tunnel_state *ts)
219
0
{
220
0
  struct httpreq *req = NULL;
221
0
  int http_minor;
222
0
  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
0
  Curl_safefree(data->req.newurl);
228
229
0
  result = Curl_http_proxy_create_CONNECT(&req, cf, data, 1);
230
0
  if(result)
231
0
    goto out;
232
233
0
  infof(data, "Establish HTTP proxy tunnel to %s", req->authority);
234
235
0
  Curl_dyn_reset(&ts->request_data);
236
0
  ts->nsent = 0;
237
0
  ts->headerlines = 0;
238
0
  http_minor = (cf->conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? 0 : 1;
239
240
0
  result = Curl_h1_req_write_head(req, http_minor, &ts->request_data);
241
242
0
out:
243
0
  if(result)
244
0
    failf(data, "Failed sending CONNECT to proxy");
245
0
  if(req)
246
0
    Curl_http_req_free(req);
247
0
  return result;
248
0
}
249
250
static CURLcode send_CONNECT(struct Curl_cfilter *cf,
251
                             struct Curl_easy *data,
252
                             struct h1_tunnel_state *ts,
253
                             bool *done)
254
0
{
255
0
  char *buf = Curl_dyn_ptr(&ts->request_data);
256
0
  size_t request_len = Curl_dyn_len(&ts->request_data);
257
0
  size_t blen = request_len;
258
0
  CURLcode result = CURLE_OK;
259
0
  ssize_t nwritten;
260
261
0
  if(blen <= ts->nsent)
262
0
    goto out;  /* we are done */
263
264
0
  blen -= ts->nsent;
265
0
  buf += ts->nsent;
266
267
0
  nwritten = cf->next->cft->do_send(cf->next, data, buf, blen, &result);
268
0
  if(nwritten < 0) {
269
0
    if(result == CURLE_AGAIN) {
270
0
      result = CURLE_OK;
271
0
    }
272
0
    goto out;
273
0
  }
274
275
0
  DEBUGASSERT(blen >= (size_t)nwritten);
276
0
  ts->nsent += (size_t)nwritten;
277
0
  Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)nwritten);
278
279
0
out:
280
0
  if(result)
281
0
    failf(data, "Failed sending CONNECT to proxy");
282
0
  *done = (!result && (ts->nsent >= request_len));
283
0
  return result;
284
0
}
285
286
static CURLcode on_resp_header(struct Curl_cfilter *cf,
287
                               struct Curl_easy *data,
288
                               struct h1_tunnel_state *ts,
289
                               const char *header)
290
0
{
291
0
  CURLcode result = CURLE_OK;
292
0
  struct SingleRequest *k = &data->req;
293
0
  (void)cf;
294
295
0
  if((checkprefix("WWW-Authenticate:", header) &&
296
0
      (401 == k->httpcode)) ||
297
0
     (checkprefix("Proxy-authenticate:", header) &&
298
0
      (407 == k->httpcode))) {
299
300
0
    bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
301
0
    char *auth = Curl_copy_header_value(header);
302
0
    if(!auth)
303
0
      return CURLE_OUT_OF_MEMORY;
304
305
0
    CURL_TRC_CF(data, cf, "CONNECT: fwd auth header '%s'", header);
306
0
    result = Curl_http_input_auth(data, proxy, auth);
307
308
0
    free(auth);
309
310
0
    if(result)
311
0
      return result;
312
0
  }
313
0
  else if(checkprefix("Content-Length:", header)) {
314
0
    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
0
      infof(data, "Ignoring Content-Length in CONNECT %03d response",
319
0
            k->httpcode);
320
0
    }
321
0
    else {
322
0
      (void)curlx_strtoofft(header + strlen("Content-Length:"),
323
0
                            NULL, 10, &ts->cl);
324
0
    }
325
0
  }
326
0
  else if(Curl_compareheader(header,
327
0
                             STRCONST("Connection:"), STRCONST("close")))
328
0
    ts->close_connection = TRUE;
329
0
  else if(checkprefix("Transfer-Encoding:", header)) {
330
0
    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
0
      infof(data, "Ignoring Transfer-Encoding in "
335
0
            "CONNECT %03d response", k->httpcode);
336
0
    }
337
0
    else if(Curl_compareheader(header,
338
0
                               STRCONST("Transfer-Encoding:"),
339
0
                               STRCONST("chunked"))) {
340
0
      infof(data, "CONNECT responded chunked");
341
0
      ts->chunked_encoding = TRUE;
342
      /* reset our chunky engine */
343
0
      Curl_httpchunk_reset(data, &ts->ch, TRUE);
344
0
    }
345
0
  }
346
0
  else if(Curl_compareheader(header,
347
0
                             STRCONST("Proxy-Connection:"),
348
0
                             STRCONST("close")))
349
0
    ts->close_connection = TRUE;
350
0
  else if(!strncmp(header, "HTTP/1.", 7) &&
351
0
          ((header[7] == '0') || (header[7] == '1')) &&
352
0
          (header[8] == ' ') &&
353
0
          ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) &&
354
0
          !ISDIGIT(header[12])) {
355
    /* store the HTTP code from the proxy */
356
0
    data->info.httpproxycode =  k->httpcode = (header[9] - '0') * 100 +
357
0
      (header[10] - '0') * 10 + (header[11] - '0');
358
0
  }
359
0
  return result;
360
0
}
361
362
static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
363
                                  struct Curl_easy *data,
364
                                  struct h1_tunnel_state *ts,
365
                                  bool *done)
366
0
{
367
0
  CURLcode result = CURLE_OK;
368
0
  struct SingleRequest *k = &data->req;
369
0
  curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data);
370
0
  char *linep;
371
0
  size_t line_len;
372
0
  int error, writetype;
373
374
0
#define SELECT_OK      0
375
0
#define SELECT_ERROR   1
376
377
0
  error = SELECT_OK;
378
0
  *done = FALSE;
379
380
0
  if(!Curl_conn_data_pending(data, cf->sockindex))
381
0
    return CURLE_OK;
382
383
0
  while(ts->keepon) {
384
0
    ssize_t nread;
385
0
    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
0
    result = Curl_read(data, tunnelsocket, &byte, 1, &nread);
390
0
    if(result == CURLE_AGAIN)
391
      /* socket buffer drained, return */
392
0
      return CURLE_OK;
393
394
0
    if(Curl_pgrsUpdate(data))
395
0
      return CURLE_ABORTED_BY_CALLBACK;
396
397
0
    if(result) {
398
0
      ts->keepon = KEEPON_DONE;
399
0
      break;
400
0
    }
401
402
0
    if(nread <= 0) {
403
0
      if(data->set.proxyauth && data->state.authproxy.avail &&
404
0
         data->state.aptr.proxyuserpwd) {
405
        /* proxy auth was requested and there was proxy auth available,
406
           then deem this as "mere" proxy disconnect */
407
0
        ts->close_connection = TRUE;
408
0
        infof(data, "Proxy CONNECT connection closed");
409
0
      }
410
0
      else {
411
0
        error = SELECT_ERROR;
412
0
        failf(data, "Proxy CONNECT aborted");
413
0
      }
414
0
      ts->keepon = KEEPON_DONE;
415
0
      break;
416
0
    }
417
418
0
    if(ts->keepon == KEEPON_IGNORE) {
419
      /* This means we are currently ignoring a response-body */
420
421
0
      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
0
        ts->cl--;
425
0
        if(ts->cl <= 0) {
426
0
          ts->keepon = KEEPON_DONE;
427
0
          break;
428
0
        }
429
0
      }
430
0
      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
0
        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
0
        result = Curl_httpchunk_read(data, &ts->ch, &byte, 1, &consumed);
438
0
        if(result)
439
0
          return result;
440
0
        if(Curl_httpchunk_is_done(data, &ts->ch)) {
441
          /* we're done reading chunks! */
442
0
          infof(data, "chunk reading DONE");
443
0
          ts->keepon = KEEPON_DONE;
444
0
        }
445
0
      }
446
0
      continue;
447
0
    }
448
449
0
    if(Curl_dyn_addn(&ts->rcvbuf, &byte, 1)) {
450
0
      failf(data, "CONNECT response too large");
451
0
      return CURLE_RECV_ERROR;
452
0
    }
453
454
    /* if this is not the end of a header line then continue */
455
0
    if(byte != 0x0a)
456
0
      continue;
457
458
0
    ts->headerlines++;
459
0
    linep = Curl_dyn_ptr(&ts->rcvbuf);
460
0
    line_len = Curl_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */
461
462
    /* output debug if that is requested */
463
0
    Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len);
464
465
    /* send the header to the callback */
466
0
    writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT |
467
0
      (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0);
468
0
    result = Curl_client_write(data, writetype, linep, line_len);
469
0
    if(result)
470
0
      return result;
471
472
0
    result = Curl_bump_headersize(data, line_len, TRUE);
473
0
    if(result)
474
0
      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
0
    if(('\r' == linep[0]) ||
481
0
       ('\n' == linep[0])) {
482
      /* end of response-headers from the proxy */
483
484
0
      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
0
        ts->keepon = KEEPON_IGNORE;
489
490
0
        if(ts->cl) {
491
0
          infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
492
0
                " bytes of response-body", ts->cl);
493
0
        }
494
0
        else if(ts->chunked_encoding) {
495
0
          infof(data, "Ignore chunked response-body");
496
0
        }
497
0
        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
0
          CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked");
502
0
          ts->keepon = KEEPON_DONE;
503
0
        }
504
0
      }
505
0
      else {
506
0
        ts->keepon = KEEPON_DONE;
507
0
      }
508
509
0
      DEBUGASSERT(ts->keepon == KEEPON_IGNORE
510
0
                  || ts->keepon == KEEPON_DONE);
511
0
      continue;
512
0
    }
513
514
0
    result = on_resp_header(cf, data, ts, linep);
515
0
    if(result)
516
0
      return result;
517
518
0
    Curl_dyn_reset(&ts->rcvbuf);
519
0
  } /* while there's buffer left and loop is requested */
520
521
0
  if(error)
522
0
    result = CURLE_RECV_ERROR;
523
0
  *done = (ts->keepon == KEEPON_DONE);
524
0
  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
0
    result = Curl_http_auth_act(data);
528
0
  }
529
0
  return result;
530
0
}
531
532
#else /* USE_HYPER */
533
534
static 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 */
569
static 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
770
error:
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
787
static 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);
814
error:
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
825
static 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
857
static CURLcode H1_CONNECT(struct Curl_cfilter *cf,
858
                           struct Curl_easy *data,
859
                           struct h1_tunnel_state *ts)
860
0
{
861
0
  struct connectdata *conn = cf->conn;
862
0
  CURLcode result;
863
0
  bool done;
864
865
0
  if(tunnel_is_established(ts))
866
0
    return CURLE_OK;
867
0
  if(tunnel_is_failed(ts))
868
0
    return CURLE_RECV_ERROR; /* Need a cfilter close and new bootstrap */
869
870
0
  do {
871
0
    timediff_t check;
872
873
0
    check = Curl_timeleft(data, NULL, TRUE);
874
0
    if(check <= 0) {
875
0
      failf(data, "Proxy CONNECT aborted due to timeout");
876
0
      result = CURLE_OPERATION_TIMEDOUT;
877
0
      goto out;
878
0
    }
879
880
0
    switch(ts->tunnel_state) {
881
0
    case H1_TUNNEL_INIT:
882
      /* Prepare the CONNECT request and make a first attempt to send. */
883
0
      CURL_TRC_CF(data, cf, "CONNECT start");
884
0
      result = start_CONNECT(cf, data, ts);
885
0
      if(result)
886
0
        goto out;
887
0
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_CONNECT, data);
888
0
      FALLTHROUGH();
889
890
0
    case H1_TUNNEL_CONNECT:
891
      /* see that the request is completely sent */
892
0
      CURL_TRC_CF(data, cf, "CONNECT send");
893
0
      result = send_CONNECT(cf, data, ts, &done);
894
0
      if(result || !done)
895
0
        goto out;
896
0
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_RECEIVE, data);
897
0
      FALLTHROUGH();
898
899
0
    case H1_TUNNEL_RECEIVE:
900
      /* read what is there */
901
0
      CURL_TRC_CF(data, cf, "CONNECT receive");
902
0
      result = recv_CONNECT_resp(cf, data, ts, &done);
903
0
      if(Curl_pgrsUpdate(data)) {
904
0
        result = CURLE_ABORTED_BY_CALLBACK;
905
0
        goto out;
906
0
      }
907
      /* error or not complete yet. return for more multi-multi */
908
0
      if(result || !done)
909
0
        goto out;
910
      /* got it */
911
0
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_RESPONSE, data);
912
0
      FALLTHROUGH();
913
914
0
    case H1_TUNNEL_RESPONSE:
915
0
      CURL_TRC_CF(data, cf, "CONNECT response");
916
0
      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
0
        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
0
          CURL_TRC_CF(data, cf, "CONNECT need to close+open");
928
0
          infof(data, "Connect me again please");
929
0
          Curl_conn_cf_close(cf, data);
930
0
          connkeep(conn, "HTTP proxy CONNECT");
931
0
          result = Curl_conn_cf_connect(cf->next, data, FALSE, &done);
932
0
          goto out;
933
0
        }
934
0
        else {
935
          /* staying on this connection, reset state */
936
0
          h1_tunnel_go_state(cf, ts, H1_TUNNEL_INIT, data);
937
0
        }
938
0
      }
939
0
      break;
940
941
0
    default:
942
0
      break;
943
0
    }
944
945
0
  } while(data->req.newurl);
946
947
0
  DEBUGASSERT(ts->tunnel_state == H1_TUNNEL_RESPONSE);
948
0
  if(data->info.httpproxycode/100 != 2) {
949
    /* a non-2xx response and we have no next url to try. */
950
0
    Curl_safefree(data->req.newurl);
951
    /* failure, close this connection to avoid reuse */
952
0
    streamclose(conn, "proxy CONNECT failure");
953
0
    h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
954
0
    failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode);
955
0
    return CURLE_RECV_ERROR;
956
0
  }
957
  /* 2xx response, SUCCESS! */
958
0
  h1_tunnel_go_state(cf, ts, H1_TUNNEL_ESTABLISHED, data);
959
0
  infof(data, "CONNECT tunnel established, response %d",
960
0
        data->info.httpproxycode);
961
0
  result = CURLE_OK;
962
963
0
out:
964
0
  if(result)
965
0
    h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
966
0
  return result;
967
0
}
968
969
static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf,
970
                                    struct Curl_easy *data,
971
                                    bool blocking, bool *done)
972
0
{
973
0
  CURLcode result;
974
0
  struct h1_tunnel_state *ts = cf->ctx;
975
976
0
  if(cf->connected) {
977
0
    *done = TRUE;
978
0
    return CURLE_OK;
979
0
  }
980
981
0
  CURL_TRC_CF(data, cf, "connect");
982
0
  result = cf->next->cft->do_connect(cf->next, data, blocking, done);
983
0
  if(result || !*done)
984
0
    return result;
985
986
0
  *done = FALSE;
987
0
  if(!ts) {
988
0
    result = tunnel_init(cf, data, &ts);
989
0
    if(result)
990
0
      return result;
991
0
    cf->ctx = ts;
992
0
  }
993
994
  /* TODO: can we do blocking? */
995
  /* We want "seamless" operations through HTTP proxy tunnel */
996
997
0
  result = H1_CONNECT(cf, data, ts);
998
0
  if(result)
999
0
    goto out;
1000
0
  Curl_safefree(data->state.aptr.proxyuserpwd);
1001
1002
0
out:
1003
0
  *done = (result == CURLE_OK) && tunnel_is_established(cf->ctx);
1004
0
  if(*done) {
1005
0
    cf->connected = TRUE;
1006
    /* Restore `data->req` fields that may habe been touched */
1007
0
    data->req.header = TRUE; /* assume header */
1008
0
    data->req.bytecount = 0;
1009
0
    data->req.ignorebody = FALSE;
1010
0
    Curl_client_cleanup(data);
1011
0
    Curl_pgrsSetUploadCounter(data, 0);
1012
0
    Curl_pgrsSetDownloadCounter(data, 0);
1013
1014
0
    tunnel_free(cf, data);
1015
0
  }
1016
0
  return result;
1017
0
}
1018
1019
static void cf_h1_proxy_adjust_pollset(struct Curl_cfilter *cf,
1020
                                        struct Curl_easy *data,
1021
                                        struct easy_pollset *ps)
1022
0
{
1023
0
  struct h1_tunnel_state *ts = cf->ctx;
1024
1025
0
  if(!cf->connected) {
1026
    /* If we are not connected, but the filter "below" is
1027
     * and not waiting on something, we are tunneling. */
1028
0
    curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
1029
0
    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
0
      if(ts->CONNECT.sending == HTTPSEND_REQUEST)
1035
0
        Curl_pollset_set_out_only(data, ps, sock);
1036
0
      else
1037
0
        Curl_pollset_set_in_only(data, ps, sock);
1038
0
    }
1039
0
    else
1040
0
      Curl_pollset_set_out_only(data, ps, sock);
1041
0
  }
1042
0
}
1043
1044
static void cf_h1_proxy_destroy(struct Curl_cfilter *cf,
1045
                                struct Curl_easy *data)
1046
0
{
1047
0
  CURL_TRC_CF(data, cf, "destroy");
1048
0
  tunnel_free(cf, data);
1049
0
}
1050
1051
static void cf_h1_proxy_close(struct Curl_cfilter *cf,
1052
                              struct Curl_easy *data)
1053
0
{
1054
0
  CURL_TRC_CF(data, cf, "close");
1055
0
  cf->connected = FALSE;
1056
0
  if(cf->ctx) {
1057
0
    h1_tunnel_go_state(cf, cf->ctx, H1_TUNNEL_INIT, data);
1058
0
  }
1059
0
  if(cf->next)
1060
0
    cf->next->cft->do_close(cf->next, data);
1061
0
}
1062
1063
1064
struct 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
1082
CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at,
1083
                                       struct Curl_easy *data)
1084
0
{
1085
0
  struct Curl_cfilter *cf;
1086
0
  CURLcode result;
1087
1088
0
  (void)data;
1089
0
  result = Curl_cf_create(&cf, &Curl_cft_h1_proxy, NULL);
1090
0
  if(!result)
1091
0
    Curl_conn_cf_insert_after(cf_at, cf);
1092
0
  return result;
1093
0
}
1094
1095
#endif /* !CURL_DISABLE_PROXY && ! CURL_DISABLE_HTTP */