Coverage Report

Created: 2026-01-10 07:08

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