Coverage Report

Created: 2026-04-28 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/cf-h1-proxy.c
Line
Count
Source
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
#include "curl_setup.h"
25
26
#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
27
28
#include "urldata.h"
29
#include "curlx/dynbuf.h"
30
#include "sendf.h"
31
#include "http.h"
32
#include "http1.h"
33
#include "http_proxy.h"
34
#include "select.h"
35
#include "progress.h"
36
#include "cfilters.h"
37
#include "cf-h1-proxy.h"
38
#include "connect.h"
39
#include "curl_trc.h"
40
#include "strcase.h"
41
#include "curlx/strparse.h"
42
43
44
typedef enum {
45
  H1_TUNNEL_INIT,     /* init/default/no tunnel state */
46
  H1_TUNNEL_CONNECT,  /* CONNECT request is being send */
47
  H1_TUNNEL_RECEIVE,  /* CONNECT answer is being received */
48
  H1_TUNNEL_RESPONSE, /* CONNECT response received completely */
49
  H1_TUNNEL_ESTABLISHED,
50
  H1_TUNNEL_FAILED
51
} h1_tunnel_state;
52
53
/* struct for HTTP CONNECT tunneling */
54
struct h1_tunnel_state {
55
  struct dynbuf rcvbuf;
56
  struct dynbuf request_data;
57
  size_t nsent;
58
  size_t headerlines;
59
  struct Curl_chunker ch;
60
  enum keeponval {
61
    KEEPON_DONE,
62
    KEEPON_CONNECT,
63
    KEEPON_IGNORE
64
  } keepon;
65
  curl_off_t cl; /* size of content to read and ignore */
66
  h1_tunnel_state tunnel_state;
67
  BIT(chunked_encoding);
68
  BIT(close_connection);
69
  BIT(maybe_folded);
70
  BIT(leading_unfold);
71
};
72
73
static bool tunnel_is_established(struct h1_tunnel_state *ts)
74
14.4M
{
75
14.4M
  return ts && (ts->tunnel_state == H1_TUNNEL_ESTABLISHED);
76
14.4M
}
77
78
static bool tunnel_is_failed(struct h1_tunnel_state *ts)
79
7.24M
{
80
7.24M
  return ts && (ts->tunnel_state == H1_TUNNEL_FAILED);
81
7.24M
}
82
83
static CURLcode tunnel_reinit(struct Curl_cfilter *cf,
84
                              struct Curl_easy *data,
85
                              struct h1_tunnel_state *ts)
86
31.8k
{
87
31.8k
  (void)data;
88
31.8k
  (void)cf;
89
31.8k
  DEBUGASSERT(ts);
90
31.8k
  curlx_dyn_reset(&ts->rcvbuf);
91
31.8k
  curlx_dyn_reset(&ts->request_data);
92
31.8k
  ts->tunnel_state = H1_TUNNEL_INIT;
93
31.8k
  ts->keepon = KEEPON_CONNECT;
94
31.8k
  ts->cl = 0;
95
31.8k
  ts->close_connection = FALSE;
96
31.8k
  ts->maybe_folded = FALSE;
97
31.8k
  ts->leading_unfold = FALSE;
98
31.8k
  return CURLE_OK;
99
31.8k
}
100
101
static CURLcode tunnel_init(struct Curl_cfilter *cf,
102
                            struct Curl_easy *data,
103
                            struct h1_tunnel_state **pts)
104
19.0k
{
105
19.0k
  struct h1_tunnel_state *ts;
106
107
19.0k
  if(cf->conn->scheme->flags & PROTOPT_NOTCPPROXY) {
108
22
    failf(data, "%s cannot be done over CONNECT", cf->conn->scheme->name);
109
22
    return CURLE_UNSUPPORTED_PROTOCOL;
110
22
  }
111
112
19.0k
  ts = curlx_calloc(1, sizeof(*ts));
113
19.0k
  if(!ts)
114
0
    return CURLE_OUT_OF_MEMORY;
115
116
19.0k
  infof(data, "allocate connect buffer");
117
118
19.0k
  curlx_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS);
119
19.0k
  curlx_dyn_init(&ts->request_data, DYN_HTTP_REQUEST);
120
19.0k
  Curl_httpchunk_init(data, &ts->ch, TRUE);
121
122
19.0k
  *pts = ts;
123
19.0k
  return tunnel_reinit(cf, data, ts);
124
19.0k
}
125
126
static void h1_tunnel_go_state(struct Curl_cfilter *cf,
127
                               struct h1_tunnel_state *ts,
128
                               h1_tunnel_state new_state,
129
                               struct Curl_easy *data)
130
95.2k
{
131
95.2k
  if(ts->tunnel_state == new_state)
132
0
    return;
133
  /* entering this one */
134
95.2k
  switch(new_state) {
135
12.8k
  case H1_TUNNEL_INIT:
136
12.8k
    CURL_TRC_CF(data, cf, "new tunnel state 'init'");
137
12.8k
    tunnel_reinit(cf, data, ts);
138
12.8k
    break;
139
140
18.8k
  case H1_TUNNEL_CONNECT:
141
18.8k
    CURL_TRC_CF(data, cf, "new tunnel state 'connect'");
142
18.8k
    ts->tunnel_state = H1_TUNNEL_CONNECT;
143
18.8k
    ts->keepon = KEEPON_CONNECT;
144
18.8k
    curlx_dyn_reset(&ts->rcvbuf);
145
18.8k
    break;
146
147
18.8k
  case H1_TUNNEL_RECEIVE:
148
18.8k
    CURL_TRC_CF(data, cf, "new tunnel state 'receive'");
149
18.8k
    ts->tunnel_state = H1_TUNNEL_RECEIVE;
150
18.8k
    break;
151
152
6.89k
  case H1_TUNNEL_RESPONSE:
153
6.89k
    CURL_TRC_CF(data, cf, "new tunnel state 'response'");
154
6.89k
    ts->tunnel_state = H1_TUNNEL_RESPONSE;
155
6.89k
    break;
156
157
6.20k
  case H1_TUNNEL_ESTABLISHED:
158
6.20k
    CURL_TRC_CF(data, cf, "new tunnel state 'established'");
159
6.20k
    infof(data, "CONNECT phase completed");
160
6.20k
    data->state.authproxy.done = TRUE;
161
6.20k
    data->state.authproxy.multipass = FALSE;
162
6.20k
    FALLTHROUGH();
163
37.7k
  case H1_TUNNEL_FAILED:
164
37.7k
    if(new_state == H1_TUNNEL_FAILED)
165
31.5k
      CURL_TRC_CF(data, cf, "new tunnel state 'failed'");
166
37.7k
    ts->tunnel_state = new_state;
167
37.7k
    curlx_dyn_reset(&ts->rcvbuf);
168
37.7k
    curlx_dyn_reset(&ts->request_data);
169
    /* restore the protocol pointer */
170
37.7k
    data->info.httpcode = 0; /* clear it as it might have been used for the
171
                                proxy */
172
    /* If a proxy-authorization header was used for the proxy, then we should
173
       make sure that it is not accidentally used for the document request
174
       after we have connected. Let's thus free and clear it here. */
175
37.7k
    curlx_safefree(data->req.proxyuserpwd);
176
37.7k
    break;
177
95.2k
  }
178
95.2k
}
179
180
static void tunnel_free(struct Curl_cfilter *cf,
181
                        struct Curl_easy *data)
182
25.2k
{
183
25.2k
  if(cf) {
184
25.2k
    struct h1_tunnel_state *ts = cf->ctx;
185
25.2k
    if(ts) {
186
19.0k
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
187
19.0k
      curlx_dyn_free(&ts->rcvbuf);
188
19.0k
      curlx_dyn_free(&ts->request_data);
189
19.0k
      Curl_httpchunk_free(data, &ts->ch);
190
19.0k
      curlx_free(ts);
191
19.0k
      cf->ctx = NULL;
192
19.0k
    }
193
25.2k
  }
194
25.2k
}
195
196
static bool tunnel_want_send(struct h1_tunnel_state *ts)
197
7.22M
{
198
7.22M
  return ts->tunnel_state == H1_TUNNEL_CONNECT;
199
7.22M
}
200
201
static CURLcode start_CONNECT(struct Curl_cfilter *cf,
202
                              struct Curl_easy *data,
203
                              struct h1_tunnel_state *ts)
204
19.0k
{
205
19.0k
  struct httpreq *req = NULL;
206
19.0k
  int http_minor;
207
19.0k
  CURLcode result;
208
209
  /* This only happens if we have looped here due to authentication reasons,
210
     and we do not really use the newly cloned URL here then. Free it. */
211
19.0k
  curlx_safefree(data->req.newurl);
212
213
19.0k
  result = Curl_http_proxy_create_CONNECT(&req, cf, data, 1);
214
19.0k
  if(result)
215
173
    goto out;
216
217
18.8k
  infof(data, "Establish HTTP proxy tunnel to %s", req->authority);
218
219
18.8k
  curlx_dyn_reset(&ts->request_data);
220
18.8k
  ts->nsent = 0;
221
18.8k
  ts->headerlines = 0;
222
18.8k
  http_minor = (cf->conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? 0 : 1;
223
224
18.8k
  result = Curl_h1_req_write_head(req, http_minor, &ts->request_data);
225
18.8k
  if(!result)
226
18.8k
    result = Curl_creader_set_null(data);
227
228
19.0k
out:
229
19.0k
  if(result)
230
173
    failf(data, "Failed sending CONNECT to proxy");
231
19.0k
  if(req)
232
18.8k
    Curl_http_req_free(req);
233
19.0k
  return result;
234
18.8k
}
235
236
static CURLcode send_CONNECT(struct Curl_cfilter *cf,
237
                             struct Curl_easy *data,
238
                             struct h1_tunnel_state *ts,
239
                             bool *done)
240
18.8k
{
241
18.8k
  const uint8_t *buf = curlx_dyn_uptr(&ts->request_data);
242
18.8k
  size_t request_len = curlx_dyn_len(&ts->request_data);
243
18.8k
  size_t blen = request_len;
244
18.8k
  CURLcode result = CURLE_OK;
245
18.8k
  size_t nwritten;
246
247
18.8k
  if(blen <= ts->nsent)
248
0
    goto out;  /* we are done */
249
250
18.8k
  blen -= ts->nsent;
251
18.8k
  buf += ts->nsent;
252
253
18.8k
  result = cf->next->cft->do_send(cf->next, data, buf, blen, FALSE, &nwritten);
254
18.8k
  if(result) {
255
0
    if(result == CURLE_AGAIN)
256
0
      result = CURLE_OK;
257
0
    goto out;
258
0
  }
259
260
18.8k
  DEBUGASSERT(blen >= nwritten);
261
18.8k
  ts->nsent += nwritten;
262
18.8k
  Curl_debug(data, CURLINFO_HEADER_OUT, (const char *)buf, nwritten);
263
264
18.8k
out:
265
18.8k
  if(result)
266
0
    failf(data, "Failed sending CONNECT to proxy");
267
18.8k
  *done = (!result && (ts->nsent >= request_len));
268
18.8k
  return result;
269
18.8k
}
270
271
static CURLcode on_resp_header(struct Curl_cfilter *cf,
272
                               struct Curl_easy *data,
273
                               struct h1_tunnel_state *ts,
274
                               const char *header)
275
226k
{
276
226k
  CURLcode result = CURLE_OK;
277
226k
  struct SingleRequest *k = &data->req;
278
226k
  (void)cf;
279
280
226k
  if((checkprefix("WWW-Authenticate:", header) && (401 == k->httpcode)) ||
281
225k
     (checkprefix("Proxy-authenticate:", header) && (407 == k->httpcode))) {
282
283
934
    bool proxy = (k->httpcode == 407);
284
934
    char *auth = Curl_copy_header_value(header);
285
934
    if(!auth)
286
0
      return CURLE_OUT_OF_MEMORY;
287
288
934
    CURL_TRC_CF(data, cf, "CONNECT: fwd auth header '%s'", header);
289
934
    result = Curl_http_input_auth(data, proxy, auth);
290
291
934
    curlx_free(auth);
292
293
934
    if(result)
294
0
      return result;
295
934
  }
296
225k
  else if(checkprefix("Content-Length:", header)) {
297
1.60k
    if(k->httpcode / 100 == 2) {
298
      /* A client MUST ignore any Content-Length or Transfer-Encoding
299
         header fields received in a successful response to CONNECT.
300
         "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
301
529
      infof(data, "Ignoring Content-Length in CONNECT %03d response",
302
529
            k->httpcode);
303
529
    }
304
1.08k
    else {
305
1.08k
      const char *p = header + strlen("Content-Length:");
306
1.08k
      if(curlx_str_numblanks(&p, &ts->cl)) {
307
20
        failf(data, "Unsupported Content-Length value");
308
20
        return CURLE_WEIRD_SERVER_REPLY;
309
20
      }
310
1.08k
    }
311
1.60k
  }
312
223k
  else if(Curl_compareheader(header,
313
223k
                             STRCONST("Connection:"), STRCONST("close")))
314
507
    ts->close_connection = TRUE;
315
223k
  else if(checkprefix("Transfer-Encoding:", header)) {
316
11.3k
    if(k->httpcode / 100 == 2) {
317
      /* A client MUST ignore any Content-Length or Transfer-Encoding
318
         header fields received in a successful response to CONNECT.
319
         "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
320
1.68k
      infof(data, "Ignoring Transfer-Encoding in "
321
1.68k
            "CONNECT %03d response", k->httpcode);
322
1.68k
    }
323
9.70k
    else if(Curl_compareheader(header,
324
9.70k
                               STRCONST("Transfer-Encoding:"),
325
9.70k
                               STRCONST("chunked"))) {
326
1.62k
      infof(data, "CONNECT responded chunked");
327
1.62k
      ts->chunked_encoding = TRUE;
328
      /* reset our chunky engine */
329
1.62k
      Curl_httpchunk_reset(data, &ts->ch, TRUE);
330
1.62k
    }
331
11.3k
  }
332
211k
  else if(Curl_compareheader(header,
333
211k
                             STRCONST("Proxy-Connection:"),
334
211k
                             STRCONST("close")))
335
12
    ts->close_connection = TRUE;
336
211k
  else if(!strncmp(header, "HTTP/1.", 7) &&
337
53.8k
          ((header[7] == '0') || (header[7] == '1')) &&
338
46.3k
          (header[8] == ' ') &&
339
39.9k
          ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) &&
340
18.6k
          !ISDIGIT(header[12])) {
341
    /* store the HTTP code from the proxy */
342
15.3k
    data->info.httpproxycode = k->httpcode = ((header[9] - '0') * 100) +
343
15.3k
      ((header[10] - '0') * 10) + (header[11] - '0');
344
15.3k
  }
345
226k
  return result;
346
226k
}
347
348
static CURLcode single_header(struct Curl_cfilter *cf,
349
                              struct Curl_easy *data,
350
                              struct h1_tunnel_state *ts)
351
233k
{
352
233k
  CURLcode result = CURLE_OK;
353
233k
  const char *linep = curlx_dyn_ptr(&ts->rcvbuf);
354
233k
  size_t line_len = curlx_dyn_len(&ts->rcvbuf); /* bytes in this line */
355
233k
  const struct SingleRequest *k = &data->req;
356
233k
  int writetype;
357
233k
  ts->headerlines++;
358
359
  /* output debug if that is requested */
360
233k
  Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len);
361
362
  /* send the header to the callback */
363
233k
  writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT |
364
233k
    (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0);
365
233k
  result = Curl_client_write(data, writetype, linep, line_len);
366
233k
  if(result)
367
277
    return result;
368
369
233k
  result = Curl_bump_headersize(data, line_len, TRUE);
370
233k
  if(result)
371
0
    return result;
372
373
  /* Newlines are CRLF, so the CR is ignored as the line is not
374
     really terminated until the LF comes. Treat a following CR
375
     as end-of-headers as well.*/
376
377
233k
  if(ISNEWLINE(linep[0])) {
378
    /* end of response-headers from the proxy */
379
380
7.39k
    if((407 == k->httpcode) && !data->state.authproblem) {
381
      /* If we get a 407 response code with content length
382
         when we have no auth problem, we must ignore the
383
         whole response-body */
384
551
      ts->keepon = KEEPON_IGNORE;
385
386
551
      if(ts->cl) {
387
107
        infof(data, "Ignore %" FMT_OFF_T " bytes of response-body", ts->cl);
388
107
      }
389
444
      else if(ts->chunked_encoding) {
390
392
        infof(data, "Ignore chunked response-body");
391
392
      }
392
52
      else {
393
        /* without content-length or chunked encoding, we
394
           cannot keep the connection alive since the close is
395
           the end signal so we bail out at once instead */
396
52
        CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked");
397
52
        ts->keepon = KEEPON_DONE;
398
52
      }
399
551
    }
400
6.84k
    else {
401
6.84k
      ts->keepon = KEEPON_DONE;
402
6.84k
    }
403
404
7.39k
    DEBUGASSERT(ts->keepon == KEEPON_IGNORE ||
405
7.39k
                ts->keepon == KEEPON_DONE);
406
7.39k
    return result;
407
7.39k
  }
408
409
226k
  result = on_resp_header(cf, data, ts, linep);
410
226k
  if(result)
411
20
    return result;
412
413
226k
  curlx_dyn_reset(&ts->rcvbuf);
414
226k
  return result;
415
226k
}
416
417
static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
418
                                  struct Curl_easy *data,
419
                                  struct h1_tunnel_state *ts,
420
                                  bool *done)
421
7.24M
{
422
7.24M
  CURLcode result = CURLE_OK;
423
7.24M
  int error;
424
425
7.24M
#define SELECT_OK      0
426
7.24M
#define SELECT_ERROR   1
427
428
7.24M
  error = SELECT_OK;
429
7.24M
  *done = FALSE;
430
431
19.6M
  while(ts->keepon) {
432
19.6M
    size_t nread;
433
19.6M
    char byte;
434
435
    /* Read one byte at a time to avoid a race condition. Wait at most one
436
       second before looping to ensure continuous pgrsUpdates. */
437
19.6M
    result = Curl_conn_recv(data, cf->sockindex, &byte, 1, &nread);
438
19.6M
    if(result == CURLE_AGAIN)
439
      /* socket buffer drained, return */
440
7.22M
      return CURLE_OK;
441
442
12.4M
    if(!result)
443
12.4M
      result = Curl_pgrsUpdate(data);
444
445
12.4M
    if(result) {
446
0
      ts->keepon = KEEPON_DONE;
447
0
      break;
448
0
    }
449
450
12.4M
    if(!nread) {
451
11.2k
      if(data->set.proxyauth && data->state.authproxy.avail &&
452
5
         data->req.proxyuserpwd) {
453
        /* proxy auth was requested and there was proxy auth available,
454
           then deem this as "mere" proxy disconnect */
455
3
        ts->close_connection = TRUE;
456
3
        infof(data, "Proxy CONNECT connection closed");
457
3
      }
458
11.2k
      else {
459
11.2k
        error = SELECT_ERROR;
460
11.2k
        failf(data, "Proxy CONNECT aborted");
461
11.2k
      }
462
11.2k
      ts->keepon = KEEPON_DONE;
463
11.2k
      break;
464
11.2k
    }
465
466
12.4M
    if(ts->keepon == KEEPON_IGNORE) {
467
      /* This means we are currently ignoring a response-body */
468
211k
      if(ts->chunked_encoding) {
469
        /* chunked-encoded body, so we need to do the chunked dance
470
           properly to know when the end of the body is reached */
471
195k
        size_t consumed = 0;
472
473
        /* now parse the chunked piece of data so that we can
474
           properly tell when the stream ends */
475
195k
        result = Curl_httpchunk_read(data, &ts->ch, &byte, 1, &consumed);
476
195k
        if(result)
477
130
          return result;
478
195k
        if(Curl_httpchunk_is_done(data, &ts->ch)) {
479
          /* we are done reading chunks! */
480
10
          infof(data, "chunk reading DONE");
481
10
          ts->keepon = KEEPON_DONE;
482
10
        }
483
195k
      }
484
16.4k
      else if(ts->cl) {
485
        /* A Content-Length based body: count down the counter
486
           and make sure to break out of the loop when we are done! */
487
16.4k
        ts->cl--;
488
16.4k
        if(ts->cl <= 0) {
489
20
          ts->keepon = KEEPON_DONE;
490
20
          break;
491
20
        }
492
16.4k
      }
493
211k
      continue;
494
211k
    }
495
496
12.2M
    if(ts->maybe_folded) {
497
247k
      if(ISBLANK(byte)) {
498
20.4k
        Curl_http_to_fold(&ts->rcvbuf);
499
20.4k
        ts->leading_unfold = TRUE;
500
20.4k
      }
501
226k
      else {
502
226k
        result = single_header(cf, data, ts);
503
226k
        if(result)
504
291
          return result;
505
        /* now handle the new byte */
506
226k
      }
507
246k
      ts->maybe_folded = FALSE;
508
246k
    }
509
510
12.2M
    if(ts->leading_unfold) {
511
75.0k
      if(ISBLANK(byte))
512
        /* skip a bit brother */
513
54.6k
        continue;
514
      /* non-blank, insert a space then continue the unfolding */
515
20.3k
      if(curlx_dyn_addn(&ts->rcvbuf, " ", 1)) {
516
0
        failf(data, "CONNECT response too large");
517
0
        return CURLE_RECV_ERROR;
518
0
      }
519
20.3k
      ts->leading_unfold = FALSE;
520
20.3k
    }
521
12.1M
    if(curlx_dyn_addn(&ts->rcvbuf, &byte, 1)) {
522
15
      failf(data, "CONNECT response too large");
523
15
      return CURLE_RECV_ERROR;
524
15
    }
525
526
    /* if this is not the end of a header line then continue */
527
12.1M
    if(byte != 0x0a)
528
11.9M
      continue;
529
254k
    else {
530
254k
      const char *linep = curlx_dyn_ptr(&ts->rcvbuf);
531
254k
      size_t hlen = curlx_dyn_len(&ts->rcvbuf);
532
254k
      if(hlen && ISNEWLINE(linep[0])) {
533
        /* end of headers */
534
7.40k
        result = single_header(cf, data, ts);
535
7.40k
        if(result)
536
6
          return result;
537
7.40k
      }
538
247k
      else
539
247k
        ts->maybe_folded = TRUE;
540
254k
    }
541
542
12.1M
  } /* while there is buffer left and loop is requested */
543
544
18.1k
  if(error)
545
11.2k
    result = CURLE_RECV_ERROR;
546
18.1k
  *done = (ts->keepon == KEEPON_DONE);
547
18.1k
  if(!result && *done && data->info.httpproxycode / 100 != 2) {
548
    /* Deal with the possibly already received authenticate
549
       headers. 'newurl' is set to a new URL if we must loop. */
550
724
    result = Curl_http_auth_act(data);
551
724
  }
552
18.1k
  return result;
553
7.24M
}
554
555
static CURLcode H1_CONNECT(struct Curl_cfilter *cf,
556
                           struct Curl_easy *data,
557
                           struct h1_tunnel_state *ts)
558
7.24M
{
559
7.24M
  struct connectdata *conn = cf->conn;
560
7.24M
  CURLcode result;
561
7.24M
  bool done;
562
563
7.24M
  if(tunnel_is_established(ts))
564
0
    return CURLE_OK;
565
7.24M
  if(tunnel_is_failed(ts))
566
0
    return CURLE_RECV_ERROR; /* Need a cfilter close and new bootstrap */
567
568
7.24M
  do {
569
570
7.24M
    if(Curl_timeleft_ms(data) < 0) {
571
7
      failf(data, "Proxy CONNECT aborted due to timeout");
572
7
      result = CURLE_OPERATION_TIMEDOUT;
573
7
      goto out;
574
7
    }
575
576
7.24M
    switch(ts->tunnel_state) {
577
19.0k
    case H1_TUNNEL_INIT:
578
      /* Prepare the CONNECT request and make a first attempt to send. */
579
19.0k
      CURL_TRC_CF(data, cf, "CONNECT start");
580
19.0k
      result = start_CONNECT(cf, data, ts);
581
19.0k
      if(result)
582
173
        goto out;
583
18.8k
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_CONNECT, data);
584
18.8k
      FALLTHROUGH();
585
586
18.8k
    case H1_TUNNEL_CONNECT:
587
      /* see that the request is completely sent */
588
18.8k
      CURL_TRC_CF(data, cf, "CONNECT send");
589
18.8k
      result = send_CONNECT(cf, data, ts, &done);
590
18.8k
      if(result || !done)
591
0
        goto out;
592
18.8k
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_RECEIVE, data);
593
18.8k
      FALLTHROUGH();
594
595
7.24M
    case H1_TUNNEL_RECEIVE:
596
      /* read what is there */
597
7.24M
      CURL_TRC_CF(data, cf, "CONNECT receive");
598
7.24M
      result = recv_CONNECT_resp(cf, data, ts, &done);
599
7.24M
      if(result)
600
11.7k
        CURL_TRC_CF(data, cf, "error receiving CONNECT response: %d", result);
601
7.24M
      if(!result)
602
7.23M
        result = Curl_pgrsUpdate(data);
603
      /* error or not complete yet. return for more multi-multi */
604
7.24M
      if(result || !done)
605
7.23M
        goto out;
606
      /* got it */
607
6.89k
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_RESPONSE, data);
608
6.89k
      FALLTHROUGH();
609
610
6.89k
    case H1_TUNNEL_RESPONSE:
611
6.89k
      CURL_TRC_CF(data, cf, "CONNECT response");
612
6.89k
      if(data->req.newurl) {
613
        /* not the "final" response, we need to do a follow up request.
614
         * If the other side indicated a connection close, or if someone
615
         * else told us to close this connection, do so now.
616
         */
617
33
        Curl_req_soft_reset(&data->req, data);
618
33
        if(ts->close_connection || conn->bits.close) {
619
          /* Close this filter and the sub-chain, re-connect the
620
           * sub-chain and continue. Closing this filter will
621
           * reset our tunnel state. To avoid recursion, we return
622
           * and expect to be called again.
623
           */
624
4
          CURL_TRC_CF(data, cf, "CONNECT need to close+open");
625
4
          infof(data, "Connect me again please");
626
4
          Curl_conn_cf_close(cf, data);
627
4
          result = Curl_conn_cf_connect(cf->next, data, &done);
628
4
          goto out;
629
4
        }
630
29
        else {
631
          /* staying on this connection, reset state */
632
29
          h1_tunnel_go_state(cf, ts, H1_TUNNEL_INIT, data);
633
29
        }
634
33
      }
635
6.88k
      break;
636
637
6.88k
    default:
638
0
      break;
639
7.24M
    }
640
641
7.24M
  } while(data->req.newurl);
642
643
6.85k
  DEBUGASSERT(ts->tunnel_state == H1_TUNNEL_RESPONSE);
644
6.85k
  if(data->info.httpproxycode / 100 != 2) {
645
    /* a non-2xx response and we have no next URL to try. */
646
648
    curlx_safefree(data->req.newurl);
647
648
    h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
648
648
    failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode);
649
648
    return CURLE_COULDNT_CONNECT;
650
648
  }
651
  /* 2xx response, SUCCESS! */
652
6.20k
  h1_tunnel_go_state(cf, ts, H1_TUNNEL_ESTABLISHED, data);
653
6.20k
  infof(data, "CONNECT tunnel established, response %d",
654
6.20k
        data->info.httpproxycode);
655
6.20k
  result = CURLE_OK;
656
657
7.24M
out:
658
7.24M
  if(result)
659
11.8k
    h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
660
7.24M
  return result;
661
6.20k
}
662
663
static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf,
664
                                    struct Curl_easy *data,
665
                                    bool *done)
666
7.24M
{
667
7.24M
  CURLcode result;
668
7.24M
  struct h1_tunnel_state *ts = cf->ctx;
669
670
7.24M
  if(cf->connected) {
671
0
    *done = TRUE;
672
0
    return CURLE_OK;
673
0
  }
674
675
7.24M
  CURL_TRC_CF(data, cf, "connect");
676
7.24M
  result = cf->next->cft->do_connect(cf->next, data, done);
677
7.24M
  if(result || !*done)
678
0
    return result;
679
680
7.24M
  *done = FALSE;
681
7.24M
  if(!ts) {
682
19.0k
    result = tunnel_init(cf, data, &ts);
683
19.0k
    if(result)
684
22
      return result;
685
19.0k
    cf->ctx = ts;
686
19.0k
  }
687
688
  /* We want "seamless" operations through HTTP proxy tunnel */
689
690
7.24M
  result = H1_CONNECT(cf, data, ts);
691
7.24M
  if(result)
692
12.5k
    goto out;
693
7.23M
  curlx_safefree(data->req.proxyuserpwd);
694
695
7.24M
out:
696
7.24M
  *done = (result == CURLE_OK) && tunnel_is_established(cf->ctx);
697
7.24M
  if(*done) {
698
6.20k
    cf->connected = TRUE;
699
    /* The real request will follow the CONNECT, reset request partially */
700
6.20k
    Curl_req_soft_reset(&data->req, data);
701
6.20k
    Curl_client_reset(data);
702
6.20k
    Curl_pgrsReset(data);
703
704
6.20k
    tunnel_free(cf, data);
705
6.20k
  }
706
7.24M
  return result;
707
7.23M
}
708
709
static CURLcode cf_h1_proxy_adjust_pollset(struct Curl_cfilter *cf,
710
                                           struct Curl_easy *data,
711
                                           struct easy_pollset *ps)
712
10.4M
{
713
10.4M
  struct h1_tunnel_state *ts = cf->ctx;
714
10.4M
  CURLcode result = CURLE_OK;
715
716
10.4M
  if(!cf->connected) {
717
    /* If we are not connected, but the filter "below" is
718
     * and not waiting on something, we are tunneling. */
719
7.22M
    curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
720
7.22M
    if(ts) {
721
      /* when we have sent a CONNECT to a proxy, we should rather either
722
         wait for the socket to become readable to be able to get the
723
         response headers or if we are still sending the request, wait
724
         for write. */
725
7.22M
      if(tunnel_want_send(ts))
726
0
        result = Curl_pollset_set_out_only(data, ps, sock);
727
7.22M
      else
728
7.22M
        result = Curl_pollset_set_in_only(data, ps, sock);
729
7.22M
    }
730
0
    else
731
0
      result = Curl_pollset_set_out_only(data, ps, sock);
732
7.22M
  }
733
10.4M
  return result;
734
10.4M
}
735
736
static void cf_h1_proxy_destroy(struct Curl_cfilter *cf,
737
                                struct Curl_easy *data)
738
19.0k
{
739
19.0k
  CURL_TRC_CF(data, cf, "destroy");
740
19.0k
  tunnel_free(cf, data);
741
19.0k
}
742
743
static void cf_h1_proxy_close(struct Curl_cfilter *cf,
744
                              struct Curl_easy *data)
745
19.0k
{
746
19.0k
  CURL_TRC_CF(data, cf, "close");
747
19.0k
  if(cf) {
748
19.0k
    cf->connected = FALSE;
749
19.0k
    if(cf->ctx) {
750
12.8k
      h1_tunnel_go_state(cf, cf->ctx, H1_TUNNEL_INIT, data);
751
12.8k
    }
752
19.0k
    if(cf->next)
753
19.0k
      cf->next->cft->do_close(cf->next, data);
754
19.0k
  }
755
19.0k
}
756
757
struct Curl_cftype Curl_cft_h1_proxy = {
758
  "H1-PROXY",
759
  CF_TYPE_IP_CONNECT | CF_TYPE_PROXY,
760
  0,
761
  cf_h1_proxy_destroy,
762
  cf_h1_proxy_connect,
763
  cf_h1_proxy_close,
764
  Curl_cf_def_shutdown,
765
  cf_h1_proxy_adjust_pollset,
766
  Curl_cf_def_data_pending,
767
  Curl_cf_def_send,
768
  Curl_cf_def_recv,
769
  Curl_cf_def_cntrl,
770
  Curl_cf_def_conn_is_alive,
771
  Curl_cf_def_conn_keep_alive,
772
  Curl_cf_http_proxy_query,
773
};
774
775
CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at,
776
                                       struct Curl_easy *data)
777
19.0k
{
778
19.0k
  struct Curl_cfilter *cf;
779
19.0k
  CURLcode result;
780
781
19.0k
  (void)data;
782
19.0k
  result = Curl_cf_create(&cf, &Curl_cft_h1_proxy, NULL);
783
19.0k
  if(!result)
784
19.0k
    Curl_conn_cf_insert_after(cf_at, cf);
785
19.0k
  return result;
786
19.0k
}
787
788
#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */