Coverage Report

Created: 2025-12-04 07:04

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