Coverage Report

Created: 2025-08-29 06:10

/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
34.3k
{
85
34.3k
  return ts && (ts->tunnel_state == H1_TUNNEL_ESTABLISHED);
86
34.3k
}
87
88
static bool tunnel_is_failed(struct h1_tunnel_state *ts)
89
24.2k
{
90
24.2k
  return ts && (ts->tunnel_state == H1_TUNNEL_FAILED);
91
24.2k
}
92
93
static CURLcode tunnel_reinit(struct Curl_cfilter *cf,
94
                              struct Curl_easy *data,
95
                              struct h1_tunnel_state *ts)
96
36.6k
{
97
36.6k
  (void)data;
98
36.6k
  (void)cf;
99
36.6k
  DEBUGASSERT(ts);
100
36.6k
  curlx_dyn_reset(&ts->rcvbuf);
101
36.6k
  curlx_dyn_reset(&ts->request_data);
102
36.6k
  ts->tunnel_state = H1_TUNNEL_INIT;
103
36.6k
  ts->keepon = KEEPON_CONNECT;
104
36.6k
  ts->cl = 0;
105
36.6k
  ts->close_connection = FALSE;
106
36.6k
  return CURLE_OK;
107
36.6k
}
108
109
static CURLcode tunnel_init(struct Curl_cfilter *cf,
110
                            struct Curl_easy *data,
111
                            struct h1_tunnel_state **pts)
112
21.1k
{
113
21.1k
  struct h1_tunnel_state *ts;
114
115
21.1k
  if(cf->conn->handler->flags & PROTOPT_NOTCPPROXY) {
116
30
    failf(data, "%s cannot be done over CONNECT", cf->conn->handler->scheme);
117
30
    return CURLE_UNSUPPORTED_PROTOCOL;
118
30
  }
119
120
21.1k
  ts = calloc(1, sizeof(*ts));
121
21.1k
  if(!ts)
122
0
    return CURLE_OUT_OF_MEMORY;
123
124
21.1k
  infof(data, "allocate connect buffer");
125
126
21.1k
  curlx_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS);
127
21.1k
  curlx_dyn_init(&ts->request_data, DYN_HTTP_REQUEST);
128
21.1k
  Curl_httpchunk_init(data, &ts->ch, TRUE);
129
130
21.1k
  *pts =  ts;
131
21.1k
  connkeep(cf->conn, "HTTP proxy CONNECT");
132
21.1k
  return tunnel_reinit(cf, data, ts);
133
21.1k
}
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
111k
{
140
111k
  if(ts->tunnel_state == new_state)
141
0
    return;
142
  /* entering this one */
143
111k
  switch(new_state) {
144
15.4k
  case H1_TUNNEL_INIT:
145
15.4k
    CURL_TRC_CF(data, cf, "new tunnel state 'init'");
146
15.4k
    tunnel_reinit(cf, data, ts);
147
15.4k
    break;
148
149
22.2k
  case H1_TUNNEL_CONNECT:
150
22.2k
    CURL_TRC_CF(data, cf, "new tunnel state 'connect'");
151
22.2k
    ts->tunnel_state = H1_TUNNEL_CONNECT;
152
22.2k
    ts->keepon = KEEPON_CONNECT;
153
22.2k
    curlx_dyn_reset(&ts->rcvbuf);
154
22.2k
    break;
155
156
22.2k
  case H1_TUNNEL_RECEIVE:
157
22.2k
    CURL_TRC_CF(data, cf, "new tunnel state 'receive'");
158
22.2k
    ts->tunnel_state = H1_TUNNEL_RECEIVE;
159
22.2k
    break;
160
161
9.10k
  case H1_TUNNEL_RESPONSE:
162
9.10k
    CURL_TRC_CF(data, cf, "new tunnel state 'response'");
163
9.10k
    ts->tunnel_state = H1_TUNNEL_RESPONSE;
164
9.10k
    break;
165
166
7.00k
  case H1_TUNNEL_ESTABLISHED:
167
7.00k
    CURL_TRC_CF(data, cf, "new tunnel state 'established'");
168
7.00k
    infof(data, "CONNECT phase completed");
169
7.00k
    data->state.authproxy.done = TRUE;
170
7.00k
    data->state.authproxy.multipass = FALSE;
171
7.00k
    FALLTHROUGH();
172
42.2k
  case H1_TUNNEL_FAILED:
173
42.2k
    if(new_state == H1_TUNNEL_FAILED)
174
35.2k
      CURL_TRC_CF(data, cf, "new tunnel state 'failed'");
175
42.2k
    ts->tunnel_state = new_state;
176
42.2k
    curlx_dyn_reset(&ts->rcvbuf);
177
42.2k
    curlx_dyn_reset(&ts->request_data);
178
    /* restore the protocol pointer */
179
42.2k
    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
42.2k
    Curl_safefree(data->state.aptr.proxyuserpwd);
185
42.2k
    break;
186
111k
  }
187
111k
}
188
189
static void tunnel_free(struct Curl_cfilter *cf,
190
                        struct Curl_easy *data)
191
28.1k
{
192
28.1k
  if(cf) {
193
28.1k
    struct h1_tunnel_state *ts = cf->ctx;
194
28.1k
    if(ts) {
195
21.1k
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
196
21.1k
      curlx_dyn_free(&ts->rcvbuf);
197
21.1k
      curlx_dyn_free(&ts->request_data);
198
21.1k
      Curl_httpchunk_free(data, &ts->ch);
199
21.1k
      free(ts);
200
21.1k
      cf->ctx = NULL;
201
21.1k
    }
202
28.1k
  }
203
28.1k
}
204
205
static bool tunnel_want_send(struct h1_tunnel_state *ts)
206
3.14k
{
207
3.14k
  return ts->tunnel_state == H1_TUNNEL_CONNECT;
208
3.14k
}
209
210
static CURLcode start_CONNECT(struct Curl_cfilter *cf,
211
                              struct Curl_easy *data,
212
                              struct h1_tunnel_state *ts)
213
22.4k
{
214
22.4k
  struct httpreq *req = NULL;
215
22.4k
  int http_minor;
216
22.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
22.4k
  Curl_safefree(data->req.newurl);
222
223
22.4k
  result = Curl_http_proxy_create_CONNECT(&req, cf, data, 1);
224
22.4k
  if(result)
225
218
    goto out;
226
227
22.2k
  infof(data, "Establish HTTP proxy tunnel to %s", req->authority);
228
229
22.2k
  curlx_dyn_reset(&ts->request_data);
230
22.2k
  ts->nsent = 0;
231
22.2k
  ts->headerlines = 0;
232
22.2k
  http_minor = (cf->conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? 0 : 1;
233
234
22.2k
  result = Curl_h1_req_write_head(req, http_minor, &ts->request_data);
235
22.2k
  if(!result)
236
22.2k
    result = Curl_creader_set_null(data);
237
238
22.4k
out:
239
22.4k
  if(result)
240
218
    failf(data, "Failed sending CONNECT to proxy");
241
22.4k
  if(req)
242
22.2k
    Curl_http_req_free(req);
243
22.4k
  return result;
244
22.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
22.2k
{
251
22.2k
  char *buf = curlx_dyn_ptr(&ts->request_data);
252
22.2k
  size_t request_len = curlx_dyn_len(&ts->request_data);
253
22.2k
  size_t blen = request_len;
254
22.2k
  CURLcode result = CURLE_OK;
255
22.2k
  size_t nwritten;
256
257
22.2k
  if(blen <= ts->nsent)
258
0
    goto out;  /* we are done */
259
260
22.2k
  blen -= ts->nsent;
261
22.2k
  buf += ts->nsent;
262
263
22.2k
  result = cf->next->cft->do_send(cf->next, data, buf, blen, FALSE, &nwritten);
264
22.2k
  if(result) {
265
0
    if(result == CURLE_AGAIN)
266
0
      result = CURLE_OK;
267
0
    goto out;
268
0
  }
269
270
22.2k
  DEBUGASSERT(blen >= nwritten);
271
22.2k
  ts->nsent += nwritten;
272
22.2k
  Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)nwritten);
273
274
22.2k
out:
275
22.2k
  if(result)
276
0
    failf(data, "Failed sending CONNECT to proxy");
277
22.2k
  *done = (!result && (ts->nsent >= request_len));
278
22.2k
  return result;
279
22.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
218k
{
286
218k
  CURLcode result = CURLE_OK;
287
218k
  struct SingleRequest *k = &data->req;
288
218k
  (void)cf;
289
290
218k
  if((checkprefix("WWW-Authenticate:", header) &&
291
218k
      (401 == k->httpcode)) ||
292
218k
     (checkprefix("Proxy-authenticate:", header) &&
293
215k
      (407 == k->httpcode))) {
294
295
4.19k
    bool proxy = (k->httpcode == 407);
296
4.19k
    char *auth = Curl_copy_header_value(header);
297
4.19k
    if(!auth)
298
0
      return CURLE_OUT_OF_MEMORY;
299
300
4.19k
    CURL_TRC_CF(data, cf, "CONNECT: fwd auth header '%s'", header);
301
4.19k
    result = Curl_http_input_auth(data, proxy, auth);
302
303
4.19k
    free(auth);
304
305
4.19k
    if(result)
306
0
      return result;
307
4.19k
  }
308
214k
  else if(checkprefix("Content-Length:", header)) {
309
3.16k
    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
966
      infof(data, "Ignoring Content-Length in CONNECT %03d response",
314
966
            k->httpcode);
315
966
    }
316
2.20k
    else {
317
2.20k
      const char *p = header + strlen("Content-Length:");
318
2.20k
      if(curlx_str_numblanks(&p, &ts->cl)) {
319
65
        failf(data, "Unsupported Content-Length value");
320
65
        return CURLE_WEIRD_SERVER_REPLY;
321
65
      }
322
2.20k
    }
323
3.16k
  }
324
211k
  else if(Curl_compareheader(header,
325
211k
                             STRCONST("Connection:"), STRCONST("close")))
326
248
    ts->close_connection = TRUE;
327
210k
  else if(checkprefix("Transfer-Encoding:", header)) {
328
9.26k
    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
1.80k
      infof(data, "Ignoring Transfer-Encoding in "
333
1.80k
            "CONNECT %03d response", k->httpcode);
334
1.80k
    }
335
7.46k
    else if(Curl_compareheader(header,
336
7.46k
                               STRCONST("Transfer-Encoding:"),
337
7.46k
                               STRCONST("chunked"))) {
338
1.05k
      infof(data, "CONNECT responded chunked");
339
1.05k
      ts->chunked_encoding = TRUE;
340
      /* reset our chunky engine */
341
1.05k
      Curl_httpchunk_reset(data, &ts->ch, TRUE);
342
1.05k
    }
343
9.26k
  }
344
201k
  else if(Curl_compareheader(header,
345
201k
                             STRCONST("Proxy-Connection:"),
346
201k
                             STRCONST("close")))
347
19
    ts->close_connection = TRUE;
348
201k
  else if(!strncmp(header, "HTTP/1.", 7) &&
349
201k
          ((header[7] == '0') || (header[7] == '1')) &&
350
201k
          (header[8] == ' ') &&
351
201k
          ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) &&
352
201k
          !ISDIGIT(header[12])) {
353
    /* store the HTTP code from the proxy */
354
14.3k
    data->info.httpproxycode =  k->httpcode = (header[9] - '0') * 100 +
355
14.3k
      (header[10] - '0') * 10 + (header[11] - '0');
356
14.3k
  }
357
218k
  return result;
358
218k
}
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
25.3k
{
365
25.3k
  CURLcode result = CURLE_OK;
366
25.3k
  struct SingleRequest *k = &data->req;
367
25.3k
  char *linep;
368
25.3k
  size_t line_len;
369
25.3k
  int error, writetype;
370
371
25.3k
#define SELECT_OK      0
372
25.3k
#define SELECT_ERROR   1
373
374
25.3k
  error = SELECT_OK;
375
25.3k
  *done = FALSE;
376
377
13.1M
  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
3.13k
      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
12.4k
      if(data->set.proxyauth && data->state.authproxy.avail &&
398
12.4k
         data->state.aptr.proxyuserpwd) {
399
        /* proxy auth was requested and there was proxy auth available,
400
           then deem this as "mere" proxy disconnect */
401
11
        ts->close_connection = TRUE;
402
11
        infof(data, "Proxy CONNECT connection closed");
403
11
      }
404
12.4k
      else {
405
12.4k
        error = SELECT_ERROR;
406
12.4k
        failf(data, "Proxy CONNECT aborted");
407
12.4k
      }
408
12.4k
      ts->keepon = KEEPON_DONE;
409
12.4k
      break;
410
12.4k
    }
411
412
13.0M
    if(ts->keepon == KEEPON_IGNORE) {
413
      /* This means we are currently ignoring a response-body */
414
415
125k
      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
31.6k
        ts->cl--;
419
31.6k
        if(ts->cl <= 0) {
420
50
          ts->keepon = KEEPON_DONE;
421
50
          break;
422
50
        }
423
31.6k
      }
424
94.1k
      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
94.1k
        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
94.1k
        result = Curl_httpchunk_read(data, &ts->ch, &byte, 1, &consumed);
432
94.1k
        if(result)
433
110
          return result;
434
94.0k
        if(Curl_httpchunk_is_done(data, &ts->ch)) {
435
          /* we are done reading chunks! */
436
54
          infof(data, "chunk reading DONE");
437
54
          ts->keepon = KEEPON_DONE;
438
54
        }
439
94.0k
      }
440
125k
      continue;
441
125k
    }
442
443
12.9M
    if(curlx_dyn_addn(&ts->rcvbuf, &byte, 1)) {
444
18
      failf(data, "CONNECT response too large");
445
18
      return CURLE_RECV_ERROR;
446
18
    }
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
228k
    ts->headerlines++;
453
228k
    linep = curlx_dyn_ptr(&ts->rcvbuf);
454
228k
    line_len = curlx_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */
455
456
    /* output debug if that is requested */
457
228k
    Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len);
458
459
    /* send the header to the callback */
460
228k
    writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT |
461
228k
      (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0);
462
228k
    result = Curl_client_write(data, writetype, linep, line_len);
463
228k
    if(result)
464
283
      return result;
465
466
228k
    result = Curl_bump_headersize(data, line_len, TRUE);
467
228k
    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
228k
    if(('\r' == linep[0]) ||
475
228k
       ('\n' == linep[0])) {
476
      /* end of response-headers from the proxy */
477
478
9.75k
      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
790
        ts->keepon = KEEPON_IGNORE;
483
484
790
        if(ts->cl) {
485
252
          infof(data, "Ignore %" FMT_OFF_T " bytes of response-body", ts->cl);
486
252
        }
487
538
        else if(ts->chunked_encoding) {
488
387
          infof(data, "Ignore chunked response-body");
489
387
        }
490
151
        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
151
          CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked");
495
151
          ts->keepon = KEEPON_DONE;
496
151
        }
497
790
      }
498
8.96k
      else {
499
8.96k
        ts->keepon = KEEPON_DONE;
500
8.96k
      }
501
502
9.75k
      DEBUGASSERT(ts->keepon == KEEPON_IGNORE
503
9.75k
                  || ts->keepon == KEEPON_DONE);
504
9.75k
      continue;
505
9.75k
    }
506
507
218k
    result = on_resp_header(cf, data, ts, linep);
508
218k
    if(result)
509
65
      return result;
510
511
218k
    curlx_dyn_reset(&ts->rcvbuf);
512
218k
  } /* while there is buffer left and loop is requested */
513
514
21.7k
  if(error)
515
12.4k
    result = CURLE_RECV_ERROR;
516
21.7k
  *done = (ts->keepon == KEEPON_DONE);
517
21.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.22k
    result = Curl_http_auth_act(data);
521
2.22k
  }
522
21.7k
  return result;
523
25.3k
}
524
525
static CURLcode H1_CONNECT(struct Curl_cfilter *cf,
526
                           struct Curl_easy *data,
527
                           struct h1_tunnel_state *ts)
528
24.2k
{
529
24.2k
  struct connectdata *conn = cf->conn;
530
24.2k
  CURLcode result;
531
24.2k
  bool done;
532
533
24.2k
  if(tunnel_is_established(ts))
534
0
    return CURLE_OK;
535
24.2k
  if(tunnel_is_failed(ts))
536
0
    return CURLE_RECV_ERROR; /* Need a cfilter close and new bootstrap */
537
538
25.5k
  do {
539
25.5k
    timediff_t check;
540
541
25.5k
    check = Curl_timeleft(data, NULL, TRUE);
542
25.5k
    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
25.5k
    switch(ts->tunnel_state) {
549
22.4k
    case H1_TUNNEL_INIT:
550
      /* Prepare the CONNECT request and make a first attempt to send. */
551
22.4k
      CURL_TRC_CF(data, cf, "CONNECT start");
552
22.4k
      result = start_CONNECT(cf, data, ts);
553
22.4k
      if(result)
554
218
        goto out;
555
22.2k
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_CONNECT, data);
556
22.2k
      FALLTHROUGH();
557
558
22.2k
    case H1_TUNNEL_CONNECT:
559
      /* see that the request is completely sent */
560
22.2k
      CURL_TRC_CF(data, cf, "CONNECT send");
561
22.2k
      result = send_CONNECT(cf, data, ts, &done);
562
22.2k
      if(result || !done)
563
0
        goto out;
564
22.2k
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_RECEIVE, data);
565
22.2k
      FALLTHROUGH();
566
567
25.3k
    case H1_TUNNEL_RECEIVE:
568
      /* read what is there */
569
25.3k
      CURL_TRC_CF(data, cf, "CONNECT receive");
570
25.3k
      result = recv_CONNECT_resp(cf, data, ts, &done);
571
25.3k
      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
25.3k
      if(result || !done)
577
16.2k
        goto out;
578
      /* got it */
579
9.10k
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_RESPONSE, data);
580
9.10k
      FALLTHROUGH();
581
582
9.10k
    case H1_TUNNEL_RESPONSE:
583
9.10k
      CURL_TRC_CF(data, cf, "CONNECT response");
584
9.10k
      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.34k
        Curl_req_soft_reset(&data->req, data);
590
1.34k
        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
6
          CURL_TRC_CF(data, cf, "CONNECT need to close+open");
597
6
          infof(data, "Connect me again please");
598
6
          Curl_conn_cf_close(cf, data);
599
6
          connkeep(conn, "HTTP proxy CONNECT");
600
6
          result = Curl_conn_cf_connect(cf->next, data, &done);
601
6
          goto out;
602
6
        }
603
1.33k
        else {
604
          /* staying on this connection, reset state */
605
1.33k
          h1_tunnel_go_state(cf, ts, H1_TUNNEL_INIT, data);
606
1.33k
        }
607
1.34k
      }
608
9.09k
      break;
609
610
9.09k
    default:
611
0
      break;
612
25.5k
    }
613
614
25.5k
  } while(data->req.newurl);
615
616
7.76k
  DEBUGASSERT(ts->tunnel_state == H1_TUNNEL_RESPONSE);
617
7.76k
  if(data->info.httpproxycode/100 != 2) {
618
    /* a non-2xx response and we have no next URL to try. */
619
762
    Curl_safefree(data->req.newurl);
620
    /* failure, close this connection to avoid reuse */
621
762
    streamclose(conn, "proxy CONNECT failure");
622
762
    h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
623
762
    failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode);
624
762
    return CURLE_RECV_ERROR;
625
762
  }
626
  /* 2xx response, SUCCESS! */
627
7.00k
  h1_tunnel_go_state(cf, ts, H1_TUNNEL_ESTABLISHED, data);
628
7.00k
  infof(data, "CONNECT tunnel established, response %d",
629
7.00k
        data->info.httpproxycode);
630
7.00k
  result = CURLE_OK;
631
632
23.4k
out:
633
23.4k
  if(result)
634
13.3k
    h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
635
23.4k
  return result;
636
7.00k
}
637
638
static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf,
639
                                    struct Curl_easy *data,
640
                                    bool *done)
641
24.2k
{
642
24.2k
  CURLcode result;
643
24.2k
  struct h1_tunnel_state *ts = cf->ctx;
644
645
24.2k
  if(cf->connected) {
646
0
    *done = TRUE;
647
0
    return CURLE_OK;
648
0
  }
649
650
24.2k
  CURL_TRC_CF(data, cf, "connect");
651
24.2k
  result = cf->next->cft->do_connect(cf->next, data, done);
652
24.2k
  if(result || !*done)
653
0
    return result;
654
655
24.2k
  *done = FALSE;
656
24.2k
  if(!ts) {
657
21.1k
    result = tunnel_init(cf, data, &ts);
658
21.1k
    if(result)
659
30
      return result;
660
21.1k
    cf->ctx = ts;
661
21.1k
  }
662
663
  /* We want "seamless" operations through HTTP proxy tunnel */
664
665
24.2k
  result = H1_CONNECT(cf, data, ts);
666
24.2k
  if(result)
667
14.0k
    goto out;
668
10.1k
  Curl_safefree(data->state.aptr.proxyuserpwd);
669
670
24.2k
out:
671
24.2k
  *done = (result == CURLE_OK) && tunnel_is_established(cf->ctx);
672
24.2k
  if(*done) {
673
7.00k
    cf->connected = TRUE;
674
    /* The real request will follow the CONNECT, reset request partially */
675
7.00k
    Curl_req_soft_reset(&data->req, data);
676
7.00k
    Curl_client_reset(data);
677
7.00k
    Curl_pgrsSetUploadCounter(data, 0);
678
7.00k
    Curl_pgrsSetDownloadCounter(data, 0);
679
680
7.00k
    tunnel_free(cf, data);
681
7.00k
  }
682
24.2k
  return result;
683
10.1k
}
684
685
static CURLcode cf_h1_proxy_adjust_pollset(struct Curl_cfilter *cf,
686
                                           struct Curl_easy *data,
687
                                           struct easy_pollset *ps)
688
2.55M
{
689
2.55M
  struct h1_tunnel_state *ts = cf->ctx;
690
2.55M
  CURLcode result = CURLE_OK;
691
692
2.55M
  if(!cf->connected) {
693
    /* If we are not connected, but the filter "below" is
694
     * and not waiting on something, we are tunneling. */
695
3.14k
    curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
696
3.14k
    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
3.14k
      if(tunnel_want_send(ts))
702
0
        result = Curl_pollset_set_out_only(data, ps, sock);
703
3.14k
      else
704
3.14k
        result = Curl_pollset_set_in_only(data, ps, sock);
705
3.14k
    }
706
0
    else
707
0
      result = Curl_pollset_set_out_only(data, ps, sock);
708
3.14k
  }
709
2.55M
  return result;
710
2.55M
}
711
712
static void cf_h1_proxy_destroy(struct Curl_cfilter *cf,
713
                                struct Curl_easy *data)
714
21.1k
{
715
21.1k
  CURL_TRC_CF(data, cf, "destroy");
716
21.1k
  tunnel_free(cf, data);
717
21.1k
}
718
719
static void cf_h1_proxy_close(struct Curl_cfilter *cf,
720
                              struct Curl_easy *data)
721
21.1k
{
722
21.1k
  CURL_TRC_CF(data, cf, "close");
723
21.1k
  if(cf) {
724
21.1k
    cf->connected = FALSE;
725
21.1k
    if(cf->ctx) {
726
14.1k
      h1_tunnel_go_state(cf, cf->ctx, H1_TUNNEL_INIT, data);
727
14.1k
    }
728
21.1k
    if(cf->next)
729
21.1k
      cf->next->cft->do_close(cf->next, data);
730
21.1k
  }
731
21.1k
}
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
21.1k
{
755
21.1k
  struct Curl_cfilter *cf;
756
21.1k
  CURLcode result;
757
758
21.1k
  (void)data;
759
21.1k
  result = Curl_cf_create(&cf, &Curl_cft_h1_proxy, NULL);
760
21.1k
  if(!result)
761
21.1k
    Curl_conn_cf_insert_after(cf_at, cf);
762
21.1k
  return result;
763
21.1k
}
764
765
#endif /* !CURL_DISABLE_PROXY && ! CURL_DISABLE_HTTP */