Coverage Report

Created: 2025-08-24 06:12

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