Coverage Report

Created: 2025-10-13 06:43

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