Coverage Report

Created: 2024-05-04 12:45

/proc/self/cwd/external/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;
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 gotbytes;
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, &gotbytes);
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(gotbytes <= 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
        ssize_t tookcareof = 0;
441
442
        /* now parse the chunked piece of data so that we can
443
           properly tell when the stream ends */
444
0
        r = Curl_httpchunk_read(data, &byte, 1, &tookcareof, &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
0
    if(!data->set.suppress_connect_headers) {
471
      /* send the header to the callback */
472
0
      int writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT |
473
0
        (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0);
474
475
0
      result = Curl_client_write(data, writetype, linep, perline);
476
0
      if(result)
477
0
        return result;
478
0
    }
479
480
0
    result = Curl_bump_headersize(data, perline, TRUE);
481
0
    if(result)
482
0
      return result;
483
484
    /* Newlines are CRLF, so the CR is ignored as the line isn't
485
       really terminated until the LF comes. Treat a following CR
486
       as end-of-headers as well.*/
487
488
0
    if(('\r' == linep[0]) ||
489
0
       ('\n' == linep[0])) {
490
      /* end of response-headers from the proxy */
491
492
0
      if((407 == k->httpcode) && !data->state.authproblem) {
493
        /* If we get a 407 response code with content length
494
           when we have no auth problem, we must ignore the
495
           whole response-body */
496
0
        ts->keepon = KEEPON_IGNORE;
497
498
0
        if(ts->cl) {
499
0
          infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
500
0
                " bytes of response-body", ts->cl);
501
0
        }
502
0
        else if(ts->chunked_encoding) {
503
0
          CHUNKcode r;
504
0
          CURLcode extra;
505
506
0
          infof(data, "Ignore chunked response-body");
507
508
          /* We set ignorebody true here since the chunked decoder
509
             function will acknowledge that. Pay attention so that this is
510
             cleared again when this function returns! */
511
0
          k->ignorebody = TRUE;
512
513
0
          if(linep[1] == '\n')
514
            /* this can only be a LF if the letter at index 0 was a CR */
515
0
            linep++;
516
517
          /* now parse the chunked piece of data so that we can properly
518
             tell when the stream ends */
519
0
          r = Curl_httpchunk_read(data, linep + 1, 1, &gotbytes,
520
0
                                  &extra);
521
0
          if(r == CHUNKE_STOP) {
522
            /* we're done reading chunks! */
523
0
            infof(data, "chunk reading DONE");
524
0
            ts->keepon = KEEPON_DONE;
525
0
          }
526
0
        }
527
0
        else {
528
          /* without content-length or chunked encoding, we
529
             can't keep the connection alive since the close is
530
             the end signal so we bail out at once instead */
531
0
          CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked");
532
0
          ts->keepon = KEEPON_DONE;
533
0
        }
534
0
      }
535
0
      else {
536
0
        ts->keepon = KEEPON_DONE;
537
0
      }
538
539
0
      DEBUGASSERT(ts->keepon == KEEPON_IGNORE
540
0
                  || ts->keepon == KEEPON_DONE);
541
0
      continue;
542
0
    }
543
544
0
    result = on_resp_header(cf, data, ts, linep);
545
0
    if(result)
546
0
      return result;
547
548
0
    Curl_dyn_reset(&ts->rcvbuf);
549
0
  } /* while there's buffer left and loop is requested */
550
551
0
  if(error)
552
0
    result = CURLE_RECV_ERROR;
553
0
  *done = (ts->keepon == KEEPON_DONE);
554
0
  if(!result && *done && data->info.httpproxycode/100 != 2) {
555
    /* Deal with the possibly already received authenticate
556
       headers. 'newurl' is set to a new URL if we must loop. */
557
0
    result = Curl_http_auth_act(data);
558
0
  }
559
0
  return result;
560
0
}
561
562
#else /* USE_HYPER */
563
564
static CURLcode CONNECT_host(struct Curl_cfilter *cf,
565
                             struct Curl_easy *data,
566
                             char **pauthority,
567
                             char **phost_header)
568
{
569
  const char *hostname;
570
  int port;
571
  bool ipv6_ip;
572
  CURLcode result;
573
  char *authority; /* for CONNECT, the destination host + port */
574
  char *host_header = NULL; /* Host: authority */
575
576
  result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip);
577
  if(result)
578
    return result;
579
580
  authority = aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
581
                      port);
582
  if(!authority)
583
    return CURLE_OUT_OF_MEMORY;
584
585
  /* If user is not overriding the Host header later */
586
  if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) {
587
    host_header = aprintf("Host: %s\r\n", authority);
588
    if(!host_header) {
589
      free(authority);
590
      return CURLE_OUT_OF_MEMORY;
591
    }
592
  }
593
  *pauthority = authority;
594
  *phost_header = host_header;
595
  return CURLE_OK;
596
}
597
598
/* The Hyper version of CONNECT */
599
static CURLcode start_CONNECT(struct Curl_cfilter *cf,
600
                              struct Curl_easy *data,
601
                              struct h1_tunnel_state *ts)
602
{
603
  struct connectdata *conn = cf->conn;
604
  struct hyptransfer *h = &data->hyp;
605
  curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data);
606
  hyper_io *io = NULL;
607
  hyper_request *req = NULL;
608
  hyper_headers *headers = NULL;
609
  hyper_clientconn_options *options = NULL;
610
  hyper_task *handshake = NULL;
611
  hyper_task *task = NULL; /* for the handshake */
612
  hyper_clientconn *client = NULL;
613
  hyper_task *sendtask = NULL; /* for the send */
614
  char *authority = NULL; /* for CONNECT */
615
  char *host_header = NULL; /* Host: */
616
  CURLcode result = CURLE_OUT_OF_MEMORY;
617
  (void)ts;
618
619
  io = hyper_io_new();
620
  if(!io) {
621
    failf(data, "Couldn't create hyper IO");
622
    result = CURLE_OUT_OF_MEMORY;
623
    goto error;
624
  }
625
  /* tell Hyper how to read/write network data */
626
  hyper_io_set_userdata(io, data);
627
  hyper_io_set_read(io, Curl_hyper_recv);
628
  hyper_io_set_write(io, Curl_hyper_send);
629
  conn->sockfd = tunnelsocket;
630
631
  data->state.hconnect = TRUE;
632
633
  /* create an executor to poll futures */
634
  if(!h->exec) {
635
    h->exec = hyper_executor_new();
636
    if(!h->exec) {
637
      failf(data, "Couldn't create hyper executor");
638
      result = CURLE_OUT_OF_MEMORY;
639
      goto error;
640
    }
641
  }
642
643
  options = hyper_clientconn_options_new();
644
  if(!options) {
645
    failf(data, "Couldn't create hyper client options");
646
    result = CURLE_OUT_OF_MEMORY;
647
    goto error;
648
  }
649
  hyper_clientconn_options_set_preserve_header_case(options, 1);
650
  hyper_clientconn_options_set_preserve_header_order(options, 1);
651
652
  hyper_clientconn_options_exec(options, h->exec);
653
654
  /* "Both the `io` and the `options` are consumed in this function
655
     call" */
656
  handshake = hyper_clientconn_handshake(io, options);
657
  if(!handshake) {
658
    failf(data, "Couldn't create hyper client handshake");
659
    result = CURLE_OUT_OF_MEMORY;
660
    goto error;
661
  }
662
  io = NULL;
663
  options = NULL;
664
665
  if(HYPERE_OK != hyper_executor_push(h->exec, handshake)) {
666
    failf(data, "Couldn't hyper_executor_push the handshake");
667
    result = CURLE_OUT_OF_MEMORY;
668
    goto error;
669
  }
670
  handshake = NULL; /* ownership passed on */
671
672
  task = hyper_executor_poll(h->exec);
673
  if(!task) {
674
    failf(data, "Couldn't hyper_executor_poll the handshake");
675
    result = CURLE_OUT_OF_MEMORY;
676
    goto error;
677
  }
678
679
  client = hyper_task_value(task);
680
  hyper_task_free(task);
681
682
  req = hyper_request_new();
683
  if(!req) {
684
    failf(data, "Couldn't hyper_request_new");
685
    result = CURLE_OUT_OF_MEMORY;
686
    goto error;
687
  }
688
  if(hyper_request_set_method(req, (uint8_t *)"CONNECT",
689
                              strlen("CONNECT"))) {
690
    failf(data, "error setting method");
691
    result = CURLE_OUT_OF_MEMORY;
692
    goto error;
693
  }
694
695
    /* This only happens if we've looped here due to authentication
696
       reasons, and we don't really use the newly cloned URL here
697
       then. Just free() it. */
698
  Curl_safefree(data->req.newurl);
699
700
  result = CONNECT_host(cf, data, &authority, &host_header);
701
  if(result)
702
    goto error;
703
704
  infof(data, "Establish HTTP proxy tunnel to %s", authority);
705
706
  if(hyper_request_set_uri(req, (uint8_t *)authority,
707
                           strlen(authority))) {
708
    failf(data, "error setting path");
709
    result = CURLE_OUT_OF_MEMORY;
710
    goto error;
711
  }
712
  if(data->set.verbose) {
713
    char *se = aprintf("CONNECT %s HTTP/1.1\r\n", authority);
714
    if(!se) {
715
      result = CURLE_OUT_OF_MEMORY;
716
      goto error;
717
    }
718
    Curl_debug(data, CURLINFO_HEADER_OUT, se, strlen(se));
719
    free(se);
720
  }
721
  /* Setup the proxy-authorization header, if any */
722
  result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET,
723
                                 authority, TRUE);
724
  if(result)
725
    goto error;
726
  Curl_safefree(authority);
727
728
  /* default is 1.1 */
729
  if((conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) &&
730
     (HYPERE_OK != hyper_request_set_version(req,
731
                                             HYPER_HTTP_VERSION_1_0))) {
732
    failf(data, "error setting HTTP version");
733
    result = CURLE_OUT_OF_MEMORY;
734
    goto error;
735
  }
736
737
  headers = hyper_request_headers(req);
738
  if(!headers) {
739
    failf(data, "hyper_request_headers");
740
    result = CURLE_OUT_OF_MEMORY;
741
    goto error;
742
  }
743
  if(host_header) {
744
    result = Curl_hyper_header(data, headers, host_header);
745
    if(result)
746
      goto error;
747
    Curl_safefree(host_header);
748
  }
749
750
  if(data->state.aptr.proxyuserpwd) {
751
    result = Curl_hyper_header(data, headers,
752
                               data->state.aptr.proxyuserpwd);
753
    if(result)
754
      goto error;
755
  }
756
757
  if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent")) &&
758
     data->set.str[STRING_USERAGENT]) {
759
    struct dynbuf ua;
760
    Curl_dyn_init(&ua, DYN_HTTP_REQUEST);
761
    result = Curl_dyn_addf(&ua, "User-Agent: %s\r\n",
762
                           data->set.str[STRING_USERAGENT]);
763
    if(result)
764
      goto error;
765
    result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&ua));
766
    if(result)
767
      goto error;
768
    Curl_dyn_free(&ua);
769
  }
770
771
  if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection"))) {
772
    result = Curl_hyper_header(data, headers,
773
                               "Proxy-Connection: Keep-Alive");
774
    if(result)
775
      goto error;
776
  }
777
778
  result = Curl_add_custom_headers(data, TRUE, headers);
779
  if(result)
780
    goto error;
781
782
  sendtask = hyper_clientconn_send(client, req);
783
  if(!sendtask) {
784
    failf(data, "hyper_clientconn_send");
785
    result = CURLE_OUT_OF_MEMORY;
786
    goto error;
787
  }
788
  req = NULL;
789
790
  if(HYPERE_OK != hyper_executor_push(h->exec, sendtask)) {
791
    failf(data, "Couldn't hyper_executor_push the send");
792
    result = CURLE_OUT_OF_MEMORY;
793
    goto error;
794
  }
795
  sendtask = NULL; /* ownership passed on */
796
797
  hyper_clientconn_free(client);
798
  client = NULL;
799
800
error:
801
  free(host_header);
802
  free(authority);
803
  if(io)
804
    hyper_io_free(io);
805
  if(options)
806
    hyper_clientconn_options_free(options);
807
  if(handshake)
808
    hyper_task_free(handshake);
809
  if(client)
810
    hyper_clientconn_free(client);
811
  if(req)
812
    hyper_request_free(req);
813
814
  return result;
815
}
816
817
static CURLcode send_CONNECT(struct Curl_cfilter *cf,
818
                             struct Curl_easy *data,
819
                             struct h1_tunnel_state *ts,
820
                             bool *done)
821
{
822
  struct hyptransfer *h = &data->hyp;
823
  struct connectdata *conn = cf->conn;
824
  hyper_task *task = NULL;
825
  hyper_error *hypererr = NULL;
826
  CURLcode result = CURLE_OK;
827
828
  (void)ts;
829
  (void)conn;
830
  do {
831
    task = hyper_executor_poll(h->exec);
832
    if(task) {
833
      bool error = hyper_task_type(task) == HYPER_TASK_ERROR;
834
      if(error)
835
        hypererr = hyper_task_value(task);
836
      hyper_task_free(task);
837
      if(error) {
838
        /* this could probably use a better error code? */
839
        result = CURLE_OUT_OF_MEMORY;
840
        goto error;
841
      }
842
    }
843
  } while(task);
844
error:
845
  *done = (result == CURLE_OK);
846
  if(hypererr) {
847
    uint8_t errbuf[256];
848
    size_t errlen = hyper_error_print(hypererr, errbuf, sizeof(errbuf));
849
    failf(data, "Hyper: %.*s", (int)errlen, errbuf);
850
    hyper_error_free(hypererr);
851
  }
852
  return result;
853
}
854
855
static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
856
                                  struct Curl_easy *data,
857
                                  struct h1_tunnel_state *ts,
858
                                  bool *done)
859
{
860
  struct hyptransfer *h = &data->hyp;
861
  CURLcode result;
862
  int didwhat;
863
864
  (void)ts;
865
  *done = FALSE;
866
  result = Curl_hyper_stream(data, cf->conn, &didwhat, done,
867
                             CURL_CSELECT_IN | CURL_CSELECT_OUT);
868
  if(result || !*done)
869
    return result;
870
  if(h->exec) {
871
    hyper_executor_free(h->exec);
872
    h->exec = NULL;
873
  }
874
  if(h->read_waker) {
875
    hyper_waker_free(h->read_waker);
876
    h->read_waker = NULL;
877
  }
878
  if(h->write_waker) {
879
    hyper_waker_free(h->write_waker);
880
    h->write_waker = NULL;
881
  }
882
  return result;
883
}
884
885
#endif /* USE_HYPER */
886
887
static CURLcode H1_CONNECT(struct Curl_cfilter *cf,
888
                           struct Curl_easy *data,
889
                           struct h1_tunnel_state *ts)
890
0
{
891
0
  struct connectdata *conn = cf->conn;
892
0
  CURLcode result;
893
0
  bool done;
894
895
0
  if(tunnel_is_established(ts))
896
0
    return CURLE_OK;
897
0
  if(tunnel_is_failed(ts))
898
0
    return CURLE_RECV_ERROR; /* Need a cfilter close and new bootstrap */
899
900
0
  do {
901
0
    timediff_t check;
902
903
0
    check = Curl_timeleft(data, NULL, TRUE);
904
0
    if(check <= 0) {
905
0
      failf(data, "Proxy CONNECT aborted due to timeout");
906
0
      result = CURLE_OPERATION_TIMEDOUT;
907
0
      goto out;
908
0
    }
909
910
0
    switch(ts->tunnel_state) {
911
0
    case H1_TUNNEL_INIT:
912
      /* Prepare the CONNECT request and make a first attempt to send. */
913
0
      CURL_TRC_CF(data, cf, "CONNECT start");
914
0
      result = start_CONNECT(cf, data, ts);
915
0
      if(result)
916
0
        goto out;
917
0
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_CONNECT, data);
918
      /* FALLTHROUGH */
919
920
0
    case H1_TUNNEL_CONNECT:
921
      /* see that the request is completely sent */
922
0
      CURL_TRC_CF(data, cf, "CONNECT send");
923
0
      result = send_CONNECT(cf, data, ts, &done);
924
0
      if(result || !done)
925
0
        goto out;
926
0
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_RECEIVE, data);
927
      /* FALLTHROUGH */
928
929
0
    case H1_TUNNEL_RECEIVE:
930
      /* read what is there */
931
0
      CURL_TRC_CF(data, cf, "CONNECT receive");
932
0
      result = recv_CONNECT_resp(cf, data, ts, &done);
933
0
      if(Curl_pgrsUpdate(data)) {
934
0
        result = CURLE_ABORTED_BY_CALLBACK;
935
0
        goto out;
936
0
      }
937
      /* error or not complete yet. return for more multi-multi */
938
0
      if(result || !done)
939
0
        goto out;
940
      /* got it */
941
0
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_RESPONSE, data);
942
      /* FALLTHROUGH */
943
944
0
    case H1_TUNNEL_RESPONSE:
945
0
      CURL_TRC_CF(data, cf, "CONNECT response");
946
0
      if(data->req.newurl) {
947
        /* not the "final" response, we need to do a follow up request.
948
         * If the other side indicated a connection close, or if someone
949
         * else told us to close this connection, do so now.
950
         */
951
0
        if(ts->close_connection || conn->bits.close) {
952
          /* Close this filter and the sub-chain, re-connect the
953
           * sub-chain and continue. Closing this filter will
954
           * reset our tunnel state. To avoid recursion, we return
955
           * and expect to be called again.
956
           */
957
0
          CURL_TRC_CF(data, cf, "CONNECT need to close+open");
958
0
          infof(data, "Connect me again please");
959
0
          Curl_conn_cf_close(cf, data);
960
0
          connkeep(conn, "HTTP proxy CONNECT");
961
0
          result = Curl_conn_cf_connect(cf->next, data, FALSE, &done);
962
0
          goto out;
963
0
        }
964
0
        else {
965
          /* staying on this connection, reset state */
966
0
          h1_tunnel_go_state(cf, ts, H1_TUNNEL_INIT, data);
967
0
        }
968
0
      }
969
0
      break;
970
971
0
    default:
972
0
      break;
973
0
    }
974
975
0
  } while(data->req.newurl);
976
977
0
  DEBUGASSERT(ts->tunnel_state == H1_TUNNEL_RESPONSE);
978
0
  if(data->info.httpproxycode/100 != 2) {
979
    /* a non-2xx response and we have no next url to try. */
980
0
    Curl_safefree(data->req.newurl);
981
    /* failure, close this connection to avoid reuse */
982
0
    streamclose(conn, "proxy CONNECT failure");
983
0
    h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
984
0
    failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode);
985
0
    return CURLE_RECV_ERROR;
986
0
  }
987
  /* 2xx response, SUCCESS! */
988
0
  h1_tunnel_go_state(cf, ts, H1_TUNNEL_ESTABLISHED, data);
989
0
  infof(data, "CONNECT tunnel established, response %d",
990
0
        data->info.httpproxycode);
991
0
  result = CURLE_OK;
992
993
0
out:
994
0
  if(result)
995
0
    h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
996
0
  return result;
997
0
}
998
999
static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf,
1000
                                    struct Curl_easy *data,
1001
                                    bool blocking, bool *done)
1002
0
{
1003
0
  CURLcode result;
1004
0
  struct h1_tunnel_state *ts = cf->ctx;
1005
1006
0
  if(cf->connected) {
1007
0
    *done = TRUE;
1008
0
    return CURLE_OK;
1009
0
  }
1010
1011
0
  CURL_TRC_CF(data, cf, "connect");
1012
0
  result = cf->next->cft->do_connect(cf->next, data, blocking, done);
1013
0
  if(result || !*done)
1014
0
    return result;
1015
1016
0
  *done = FALSE;
1017
0
  if(!ts) {
1018
0
    result = tunnel_init(cf, data, &ts);
1019
0
    if(result)
1020
0
      return result;
1021
0
    cf->ctx = ts;
1022
0
  }
1023
1024
  /* TODO: can we do blocking? */
1025
  /* We want "seamless" operations through HTTP proxy tunnel */
1026
1027
0
  result = H1_CONNECT(cf, data, ts);
1028
0
  if(result)
1029
0
    goto out;
1030
0
  Curl_safefree(data->state.aptr.proxyuserpwd);
1031
1032
0
out:
1033
0
  *done = (result == CURLE_OK) && tunnel_is_established(cf->ctx);
1034
0
  if(*done) {
1035
0
    cf->connected = TRUE;
1036
0
    tunnel_free(cf, data);
1037
0
  }
1038
0
  return result;
1039
0
}
1040
1041
static int cf_h1_proxy_get_select_socks(struct Curl_cfilter *cf,
1042
                                        struct Curl_easy *data,
1043
                                        curl_socket_t *socks)
1044
0
{
1045
0
  struct h1_tunnel_state *ts = cf->ctx;
1046
0
  int fds;
1047
1048
0
  fds = cf->next->cft->get_select_socks(cf->next, data, socks);
1049
0
  if(!fds && cf->next->connected && !cf->connected) {
1050
    /* If we are not connected, but the filter "below" is
1051
     * and not waiting on something, we are tunneling. */
1052
0
    socks[0] = Curl_conn_cf_get_socket(cf, data);
1053
0
    if(ts) {
1054
      /* when we've sent a CONNECT to a proxy, we should rather either
1055
         wait for the socket to become readable to be able to get the
1056
         response headers or if we're still sending the request, wait
1057
         for write. */
1058
0
      if(ts->CONNECT.sending == HTTPSEND_REQUEST) {
1059
0
        return GETSOCK_WRITESOCK(0);
1060
0
      }
1061
0
      return GETSOCK_READSOCK(0);
1062
0
    }
1063
0
    return GETSOCK_WRITESOCK(0);
1064
0
  }
1065
0
  return fds;
1066
0
}
1067
1068
static void cf_h1_proxy_destroy(struct Curl_cfilter *cf,
1069
                                struct Curl_easy *data)
1070
0
{
1071
0
  CURL_TRC_CF(data, cf, "destroy");
1072
0
  tunnel_free(cf, data);
1073
0
}
1074
1075
static void cf_h1_proxy_close(struct Curl_cfilter *cf,
1076
                              struct Curl_easy *data)
1077
0
{
1078
0
  CURL_TRC_CF(data, cf, "close");
1079
0
  cf->connected = FALSE;
1080
0
  if(cf->ctx) {
1081
0
    h1_tunnel_go_state(cf, cf->ctx, H1_TUNNEL_INIT, data);
1082
0
  }
1083
0
  if(cf->next)
1084
0
    cf->next->cft->do_close(cf->next, data);
1085
0
}
1086
1087
1088
struct Curl_cftype Curl_cft_h1_proxy = {
1089
  "H1-PROXY",
1090
  CF_TYPE_IP_CONNECT,
1091
  0,
1092
  cf_h1_proxy_destroy,
1093
  cf_h1_proxy_connect,
1094
  cf_h1_proxy_close,
1095
  Curl_cf_http_proxy_get_host,
1096
  cf_h1_proxy_get_select_socks,
1097
  Curl_cf_def_data_pending,
1098
  Curl_cf_def_send,
1099
  Curl_cf_def_recv,
1100
  Curl_cf_def_cntrl,
1101
  Curl_cf_def_conn_is_alive,
1102
  Curl_cf_def_conn_keep_alive,
1103
  Curl_cf_def_query,
1104
};
1105
1106
CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at,
1107
                                       struct Curl_easy *data)
1108
0
{
1109
0
  struct Curl_cfilter *cf;
1110
0
  CURLcode result;
1111
1112
0
  (void)data;
1113
0
  result = Curl_cf_create(&cf, &Curl_cft_h1_proxy, NULL);
1114
0
  if(!result)
1115
0
    Curl_conn_cf_insert_after(cf_at, cf);
1116
0
  return result;
1117
0
}
1118
1119
#endif /* !CURL_DISABLE_PROXY && ! CURL_DISABLE_HTTP */