Coverage Report

Created: 2023-12-08 06:48

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