Coverage Report

Created: 2026-08-31 06:49

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
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 "select.h"
37
#include "progress.h"
38
#include "multiif.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 "curlx/strparse.h"
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 Curl_peer *dest;
58
  struct dynbuf rcvbuf;
59
  struct dynbuf request_data;
60
  size_t nsent;
61
  size_t headerlines;
62
  struct Curl_chunker ch;
63
  int httpversion;
64
  enum keeponval {
65
    KEEPON_DONE,
66
    KEEPON_CONNECT,
67
    KEEPON_IGNORE
68
  } keepon;
69
  curl_off_t cl; /* size of content to read and ignore */
70
  h1_tunnel_state tunnel_state;
71
  BIT(chunked_encoding);
72
  BIT(close_connection);
73
  BIT(maybe_folded);
74
  BIT(leading_unfold);
75
};
76
77
/* Persistent context for the H1-PROXY filter */
78
struct cf_h1_proxy_ctx {
79
  struct h1_tunnel_state *ts;
80
  BIT(udp_tunnel);
81
};
82
83
static bool tunnel_is_established(struct h1_tunnel_state *ts)
84
44.6k
{
85
44.6k
  return ts && (ts->tunnel_state == H1_TUNNEL_ESTABLISHED);
86
44.6k
}
87
88
static bool tunnel_is_failed(struct h1_tunnel_state *ts)
89
39.1k
{
90
39.1k
  return ts && (ts->tunnel_state == H1_TUNNEL_FAILED);
91
39.1k
}
92
93
static bool h1_proxy_is_udp(struct Curl_cfilter *cf)
94
107k
{
95
107k
  struct cf_h1_proxy_ctx *pctx = cf->ctx;
96
107k
  return (pctx->udp_tunnel ? TRUE : FALSE);
97
107k
}
98
99
static CURLcode tunnel_reinit(struct Curl_cfilter *cf,
100
                              struct Curl_easy *data,
101
                              struct h1_tunnel_state *ts)
102
5
{
103
5
  (void)data;
104
5
  (void)cf;
105
5
  DEBUGASSERT(ts);
106
5
  curlx_dyn_reset(&ts->rcvbuf);
107
5
  curlx_dyn_reset(&ts->request_data);
108
5
  ts->tunnel_state = H1_TUNNEL_INIT;
109
5
  ts->keepon = KEEPON_CONNECT;
110
5
  ts->cl = 0;
111
5
  ts->close_connection = FALSE;
112
5
  ts->maybe_folded = FALSE;
113
5
  ts->leading_unfold = FALSE;
114
5
  ts->nsent = 0;
115
5
  ts->headerlines = 0;
116
5
  return CURLE_OK;
117
5
}
118
119
static CURLcode tunnel_init(struct Curl_cfilter *cf,
120
                            struct Curl_easy *data,
121
                            struct h1_tunnel_state **pts)
122
0
{
123
0
  struct h1_tunnel_state *ts;
124
125
0
  if(cf->conn->scheme->flags & PROTOPT_NOTCPPROXY) {
126
0
    failf(data, "%s cannot be done over CONNECT", cf->conn->scheme->name);
127
0
    return CURLE_UNSUPPORTED_PROTOCOL;
128
0
  }
129
130
0
  ts = curlx_calloc(1, sizeof(*ts));
131
0
  if(!ts)
132
0
    return CURLE_OUT_OF_MEMORY;
133
134
0
  infof(data, "allocate connect buffer");
135
136
0
  curlx_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS);
137
0
  curlx_dyn_init(&ts->request_data, DYN_HTTP_REQUEST);
138
0
  Curl_httpchunk_init(data, &ts->ch, TRUE, TRUE);
139
140
0
  *pts = ts;
141
0
  return tunnel_reinit(cf, data, ts);
142
0
}
143
144
static void h1_tunnel_go_state(struct Curl_cfilter *cf,
145
                               struct h1_tunnel_state *ts,
146
                               h1_tunnel_state new_state,
147
                               struct Curl_easy *data)
148
137k
{
149
137k
  if(ts->tunnel_state == new_state)
150
33.7k
    return;
151
  /* entering this one */
152
103k
  switch(new_state) {
153
5
  case H1_TUNNEL_INIT:
154
5
    CURL_TRC_CF(data, cf, "new tunnel state 'init'");
155
5
    tunnel_reinit(cf, data, ts);
156
5
    break;
157
158
34.1k
  case H1_TUNNEL_CONNECT:
159
34.1k
    CURL_TRC_CF(data, cf, "new tunnel state 'connect'");
160
34.1k
    ts->tunnel_state = H1_TUNNEL_CONNECT;
161
34.1k
    ts->keepon = KEEPON_CONNECT;
162
34.1k
    curlx_dyn_reset(&ts->rcvbuf);
163
34.1k
    break;
164
165
34.1k
  case H1_TUNNEL_RECEIVE:
166
34.1k
    CURL_TRC_CF(data, cf, "new tunnel state 'receive'");
167
34.1k
    ts->tunnel_state = H1_TUNNEL_RECEIVE;
168
34.1k
    break;
169
170
890
  case H1_TUNNEL_RESPONSE:
171
890
    CURL_TRC_CF(data, cf, "new tunnel state 'response'");
172
890
    ts->tunnel_state = H1_TUNNEL_RESPONSE;
173
890
    break;
174
175
212
  case H1_TUNNEL_ESTABLISHED:
176
212
    CURL_TRC_CF(data, cf, "new tunnel state 'established'");
177
212
    infof(data, "CONNECT%s phase completed for HTTP proxy",
178
212
          h1_proxy_is_udp(cf) ? "-UDP" : "");
179
180
212
    data->state.authproxy.done = TRUE;
181
212
    data->state.authproxy.multipass = FALSE;
182
212
    FALLTHROUGH();
183
34.4k
  case H1_TUNNEL_FAILED:
184
34.4k
    if(new_state == H1_TUNNEL_FAILED)
185
34.2k
      CURL_TRC_CF(data, cf, "new tunnel state 'failed'");
186
34.4k
    ts->tunnel_state = new_state;
187
34.4k
    curlx_dyn_reset(&ts->rcvbuf);
188
34.4k
    curlx_dyn_reset(&ts->request_data);
189
    /* restore the protocol pointer */
190
34.4k
    data->info.httpcode = 0; /* clear it as it might have been used for the
191
                                proxy */
192
    /* If a proxy-authorization header was used for the proxy, then we should
193
       make sure that it is not accidentally used for the document request
194
       after we have connected. Let's thus free and clear it here. */
195
34.4k
    curlx_safefree(data->req.hd_proxy_auth);
196
34.4k
    break;
197
103k
  }
198
103k
}
199
200
static void tunnel_free(struct h1_tunnel_state *ts,
201
                        struct Curl_easy *data)
202
68.5k
{
203
68.5k
  if(ts) {
204
34.2k
    Curl_peer_unlink(&ts->dest);
205
34.2k
    curlx_dyn_free(&ts->rcvbuf);
206
34.2k
    curlx_dyn_free(&ts->request_data);
207
34.2k
    Curl_httpchunk_free(data, &ts->ch);
208
34.2k
    curlx_free(ts);
209
34.2k
  }
210
68.5k
}
211
212
static void cf_tunnel_free(struct Curl_cfilter *cf,
213
                           struct Curl_easy *data)
214
34.4k
{
215
34.4k
  if(cf) {
216
34.4k
    struct cf_h1_proxy_ctx *pctx = cf->ctx;
217
34.4k
    struct h1_tunnel_state *ts = pctx ? pctx->ts : NULL;
218
34.4k
    if(ts) {
219
34.2k
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
220
34.2k
      tunnel_free(ts, data);
221
34.2k
      pctx->ts = NULL;
222
34.2k
    }
223
34.4k
  }
224
34.4k
}
225
226
static bool tunnel_want_send(struct h1_tunnel_state *ts)
227
5.21k
{
228
5.21k
  return ts->tunnel_state == H1_TUNNEL_CONNECT;
229
5.21k
}
230
231
static CURLcode start_CONNECT(struct Curl_cfilter *cf,
232
                              struct Curl_easy *data,
233
                              struct h1_tunnel_state *ts)
234
34.2k
{
235
34.2k
  struct httpreq *req = NULL;
236
34.2k
  int http_minor;
237
34.2k
  CURLcode result;
238
239
34.2k
  DEBUGASSERT(data);
240
  /* This only happens if we have looped here due to authentication reasons,
241
     and we do not really use the newly cloned URL here then. Free it. */
242
34.2k
  curlx_safefree(data->req.newurl);
243
244
34.2k
  result = Curl_http_proxy_create_tunnel_request(&req, cf, data, ts->dest,
245
34.2k
                                                  PROXY_HTTP_V1,
246
34.2k
                                                  h1_proxy_is_udp(cf));
247
34.2k
  if(result)
248
108
    goto out;
249
250
34.1k
  curlx_dyn_reset(&ts->request_data);
251
34.1k
  ts->nsent = 0;
252
34.1k
  ts->headerlines = 0;
253
34.1k
  http_minor = ts->httpversion % 10;
254
255
34.1k
  result = Curl_h1_req_write_head(req, http_minor, &ts->request_data);
256
34.1k
  if(!result)
257
34.1k
    result = Curl_creader_set_null(data);
258
259
34.2k
out:
260
34.2k
  if(result)
261
108
    failf(data, "Failed sending CONNECT to proxy");
262
34.2k
  if(req)
263
34.1k
    Curl_http_req_free(req);
264
34.2k
  return result;
265
34.1k
}
266
267
static CURLcode send_CONNECT(struct Curl_cfilter *cf,
268
                             struct Curl_easy *data,
269
                             struct h1_tunnel_state *ts,
270
                             bool *done)
271
34.1k
{
272
34.1k
  const uint8_t *buf = curlx_dyn_uptr(&ts->request_data);
273
34.1k
  size_t request_len = curlx_dyn_len(&ts->request_data);
274
34.1k
  size_t blen = request_len;
275
34.1k
  CURLcode result = CURLE_OK;
276
34.1k
  size_t nwritten;
277
278
34.1k
  if(blen <= ts->nsent)
279
0
    goto out;  /* we are done */
280
281
34.1k
  blen -= ts->nsent;
282
34.1k
  buf += ts->nsent;
283
284
34.1k
  result = cf->next->cft->do_send(cf->next, data, buf, blen, FALSE, &nwritten);
285
34.1k
  if(result) {
286
2
    if(result == CURLE_AGAIN)
287
2
      result = CURLE_OK;
288
2
    goto out;
289
2
  }
290
291
34.1k
  DEBUGASSERT(blen >= nwritten);
292
34.1k
  ts->nsent += nwritten;
293
34.1k
  Curl_debug(data, CURLINFO_HEADER_OUT, (const char *)buf, nwritten);
294
295
34.1k
out:
296
34.1k
  if(result)
297
0
    failf(data, "Failed sending CONNECT to proxy");
298
34.1k
  *done = (!result && (ts->nsent >= request_len));
299
34.1k
  return result;
300
34.1k
}
301
302
static CURLcode on_resp_header_udp(struct Curl_cfilter *cf,
303
                                   struct Curl_easy *data,
304
                                   struct h1_tunnel_state *ts,
305
                                   const char *header)
306
1.25k
{
307
1.25k
  CURLcode result = CURLE_OK;
308
1.25k
  struct SingleRequest *k = &data->req;
309
310
1.25k
  if((checkprefix("WWW-Authenticate:", header) && (401 == k->httpcode)) ||
311
1.25k
     (checkprefix("Proxy-authenticate:", header) && (407 == k->httpcode))) {
312
313
0
    bool proxy = (k->httpcode == 407);
314
0
    char *auth = Curl_copy_header_value(header);
315
0
    if(!auth)
316
0
      return CURLE_OUT_OF_MEMORY;
317
318
0
    CURL_TRC_CF(data, cf, "CONNECT-UDP: fwd auth header '%s'", header);
319
0
    result = Curl_http_input_auth(data, proxy, auth);
320
321
0
    curlx_free(auth);
322
323
0
    if(result)
324
0
      return result;
325
0
  }
326
1.25k
  else if(checkprefix("Content-Length:", header)) {
327
0
    if(k->httpcode / 100 == 2 || k->httpcode == 101) {
328
0
      infof(data, "Ignoring Content-Length in CONNECT-UDP %03d response",
329
0
            k->httpcode);
330
0
    }
331
0
    else {
332
0
      const char *p = header + CURL_CSTRLEN("Content-Length:");
333
0
      if(curlx_str_numblanks(&p, &ts->cl)) {
334
0
        failf(data, "Unsupported Content-Length value");
335
0
        return CURLE_WEIRD_SERVER_REPLY;
336
0
      }
337
0
    }
338
0
  }
339
1.25k
  else if(checkprefix("Transfer-Encoding:", header)) {
340
0
    if(k->httpcode / 100 == 2 || k->httpcode == 101) {
341
0
      infof(data, "Ignoring Transfer-Encoding in "
342
0
            "CONNECT-UDP %03d response", k->httpcode);
343
0
    }
344
0
    else if(Curl_compareheader(header,
345
0
                               STRCONST("Transfer-Encoding:"),
346
0
                               STRCONST("chunked"))) {
347
0
      CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> "
348
0
                  "Transfer-Encoding: chunked");
349
0
      ts->chunked_encoding = TRUE;
350
      /* reset our chunky engine */
351
0
      Curl_httpchunk_reset(data, &ts->ch, TRUE);
352
0
    }
353
0
  }
354
1.25k
  else if(checkprefix("Capsule-protocol:", header)) {
355
0
    if(Curl_compareheader(header,
356
0
                           STRCONST("Capsule-protocol:"),
357
0
                           STRCONST("?1"))) {
358
0
      CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> Capsule-protocol: ?1");
359
0
    }
360
0
  }
361
1.25k
  else if(Curl_compareheader(header,
362
1.25k
                              STRCONST("Connection:"), STRCONST("close"))) {
363
0
    ts->close_connection = TRUE;
364
0
    CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> Connection: close");
365
0
  }
366
1.25k
  else if(Curl_compareheader(header,
367
1.25k
                             STRCONST("Proxy-Connection:"),
368
1.25k
                             STRCONST("close"))) {
369
0
    ts->close_connection = TRUE;
370
0
    CURL_TRC_CF(data, cf,
371
0
                "CONNECT-UDP Response --> Proxy-Connection: close");
372
0
  }
373
1.25k
  else if(!strncmp(header, "HTTP/1.", 7) &&
374
175
           ((header[7] == '0') || (header[7] == '1')) &&
375
162
           (header[8] == ' ') &&
376
149
           ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) &&
377
82
           !ISDIGIT(header[12])) {
378
    /* store the HTTP code from the proxy */
379
72
    data->info.httpproxycode = k->httpcode =
380
72
      ((header[9] - '0') * 100) +
381
72
      ((header[10] - '0') * 10) +
382
72
      (header[11] - '0');
383
72
    CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> %d", k->httpcode);
384
72
  }
385
1.25k
  return result;
386
1.25k
}
387
388
static CURLcode on_resp_header(struct Curl_cfilter *cf,
389
                               struct Curl_easy *data,
390
                               struct h1_tunnel_state *ts,
391
                               const char *header)
392
69.7k
{
393
69.7k
  CURLcode result = CURLE_OK;
394
69.7k
  struct SingleRequest *k = &data->req;
395
69.7k
  (void)cf;
396
397
69.7k
  if((checkprefix("WWW-Authenticate:", header) && (401 == k->httpcode)) ||
398
69.5k
     (checkprefix("Proxy-authenticate:", header) && (407 == k->httpcode))) {
399
400
664
    bool proxy = (k->httpcode == 407);
401
664
    char *auth = Curl_copy_header_value(header);
402
664
    if(!auth)
403
0
      return CURLE_OUT_OF_MEMORY;
404
405
664
    CURL_TRC_CF(data, cf, "CONNECT: fwd auth header '%s'", header);
406
664
    result = Curl_http_input_auth(data, proxy, auth);
407
408
664
    curlx_free(auth);
409
410
664
    if(result)
411
0
      return result;
412
664
  }
413
69.0k
  else if(checkprefix("Content-Length:", header)) {
414
1.04k
    if(k->httpcode / 100 == 2) {
415
      /* A client MUST ignore any Content-Length or Transfer-Encoding
416
         header fields received in a successful response to CONNECT.
417
         "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
418
317
      infof(data, "Ignoring Content-Length in CONNECT %03d response",
419
317
            k->httpcode);
420
317
    }
421
731
    else {
422
731
      const char *p = header + CURL_CSTRLEN("Content-Length:");
423
731
      if(curlx_str_numblanks(&p, &ts->cl)) {
424
21
        failf(data, "Unsupported Content-Length value");
425
21
        return CURLE_WEIRD_SERVER_REPLY;
426
21
      }
427
731
    }
428
1.04k
  }
429
68.0k
  else if(Curl_compareheader(header,
430
68.0k
                             STRCONST("Connection:"), STRCONST("close")))
431
203
    ts->close_connection = TRUE;
432
67.8k
  else if(checkprefix("Transfer-Encoding:", header)) {
433
2.43k
    if(k->httpcode / 100 == 2) {
434
      /* A client MUST ignore any Content-Length or Transfer-Encoding
435
         header fields received in a successful response to CONNECT.
436
         "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
437
258
      infof(data, "Ignoring Transfer-Encoding in "
438
258
            "CONNECT %03d response", k->httpcode);
439
258
    }
440
2.18k
    else if(Curl_compareheader(header,
441
2.18k
                               STRCONST("Transfer-Encoding:"),
442
2.18k
                               STRCONST("chunked"))) {
443
891
      infof(data, "CONNECT responded chunked");
444
891
      ts->chunked_encoding = TRUE;
445
      /* reset our chunky engine */
446
891
      Curl_httpchunk_reset(data, &ts->ch, TRUE);
447
891
    }
448
2.43k
  }
449
65.3k
  else if(Curl_compareheader(header,
450
65.3k
                             STRCONST("Proxy-Connection:"),
451
65.3k
                             STRCONST("close")))
452
143
    ts->close_connection = TRUE;
453
65.2k
  else if(!strncmp(header, "HTTP/1.", 7) &&
454
7.10k
          ((header[7] == '0') || (header[7] == '1')) &&
455
6.02k
          (header[8] == ' ') &&
456
5.33k
          ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) &&
457
2.16k
          !ISDIGIT(header[12])) {
458
    /* store the HTTP code from the proxy */
459
1.88k
    data->info.httpproxycode = k->httpcode = ((header[9] - '0') * 100) +
460
1.88k
      ((header[10] - '0') * 10) + (header[11] - '0');
461
1.88k
  }
462
69.7k
  return result;
463
69.7k
}
464
465
static CURLcode single_header(struct Curl_cfilter *cf,
466
                              struct Curl_easy *data,
467
                              struct h1_tunnel_state *ts)
468
73.3k
{
469
73.3k
  CURLcode result = CURLE_OK;
470
73.3k
  const char *linep = curlx_dyn_ptr(&ts->rcvbuf);
471
73.3k
  size_t line_len = curlx_dyn_len(&ts->rcvbuf); /* bytes in this line */
472
73.3k
  const struct SingleRequest *k = &data->req;
473
73.3k
  int writetype;
474
73.3k
  ts->headerlines++;
475
476
  /* output debug if that is requested */
477
73.3k
  Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len);
478
479
  /* a CONNECT response line is handed to the client as a header, so it must
480
     pass the same checks as a regular response header before delivery */
481
73.3k
  result = Curl_verify_header(data, linep, line_len);
482
73.3k
  if(result)
483
939
    return result;
484
485
  /* send the header to the callback */
486
72.4k
  writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT |
487
72.4k
    (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0);
488
72.4k
  result = Curl_client_write(data, writetype, linep, line_len);
489
72.4k
  if(result)
490
110
    return result;
491
492
72.3k
  result = Curl_bump_headersize(data, line_len, TRUE);
493
72.3k
  if(result)
494
0
    return result;
495
496
  /* Newlines are CRLF, so the CR is ignored as the line is not
497
     really terminated until the LF comes. Treat a following CR
498
     as end-of-headers as well.*/
499
500
72.3k
  if(ISNEWLINE(linep[0])) {
501
    /* end of response-headers from the proxy */
502
503
1.35k
    if((407 == k->httpcode) && !data->state.authproblem) {
504
      /* If we get a 407 response code with content length
505
         when we have no auth problem, we must ignore the
506
         whole response-body */
507
439
      ts->keepon = KEEPON_IGNORE;
508
509
439
      if(ts->cl) {
510
118
        infof(data, "Ignore %" FMT_OFF_T " bytes of response-body", ts->cl);
511
118
      }
512
321
      else if(ts->chunked_encoding) {
513
276
        infof(data, "Ignore chunked response-body");
514
276
      }
515
45
      else {
516
        /* without content-length or chunked encoding, we
517
           cannot keep the connection alive since the close is
518
           the end signal so we bail out at once instead */
519
45
        CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked");
520
45
        ts->keepon = KEEPON_DONE;
521
45
      }
522
439
    }
523
912
    else {
524
912
      ts->keepon = KEEPON_DONE;
525
912
    }
526
527
1.35k
    DEBUGASSERT(ts->keepon == KEEPON_IGNORE ||
528
1.35k
                ts->keepon == KEEPON_DONE);
529
1.35k
    return result;
530
1.35k
  }
531
532
70.9k
  if(h1_proxy_is_udp(cf)) {
533
1.25k
    result = on_resp_header_udp(cf, data, ts, linep);
534
1.25k
  }
535
69.7k
  else {
536
69.7k
    result = on_resp_header(cf, data, ts, linep);
537
69.7k
  }
538
539
70.9k
  if(result)
540
21
    return result;
541
542
70.9k
  curlx_dyn_reset(&ts->rcvbuf);
543
70.9k
  return result;
544
70.9k
}
545
546
static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
547
                                  struct Curl_easy *data,
548
                                  struct h1_tunnel_state *ts,
549
                                  bool *done)
550
39.0k
{
551
39.0k
  CURLcode result = CURLE_OK;
552
39.0k
  int error;
553
554
39.0k
#define SELECT_OK      0
555
39.0k
#define SELECT_ERROR   1
556
557
39.0k
  error = SELECT_OK;
558
39.0k
  *done = FALSE;
559
560
3.35M
  while(ts->keepon) {
561
3.35M
    size_t nread;
562
3.35M
    char byte;
563
564
    /* Read one byte at a time to avoid a race condition. Wait at most one
565
       second before looping to ensure continuous pgrsUpdates. */
566
3.35M
    result = Curl_conn_recv(data, cf->sockindex, &byte, 1, &nread);
567
3.35M
    if(result == CURLE_AGAIN)
568
      /* socket buffer drained, return */
569
5.20k
      return CURLE_OK;
570
571
3.35M
    if(!result)
572
3.35M
      result = Curl_pgrsUpdate(data);
573
574
3.35M
    if(result) {
575
0
      ts->keepon = KEEPON_DONE;
576
0
      break;
577
0
    }
578
579
3.35M
    if(!nread) {
580
31.7k
      if(ts->maybe_folded) {
581
        /* EOF right after LF: finalize the pending header line. */
582
331
        result = single_header(cf, data, ts);
583
331
        if(result)
584
79
          return result;
585
252
        ts->maybe_folded = FALSE;
586
252
      }
587
31.6k
      if(data->set.proxyauth && data->state.authproxy.avail &&
588
7
         data->req.hd_proxy_auth) {
589
        /* proxy auth was requested and there was proxy auth available,
590
           then deem this as "mere" proxy disconnect */
591
0
        ts->close_connection = TRUE;
592
0
        infof(data, "Proxy CONNECT connection closed");
593
0
      }
594
31.6k
      else {
595
31.6k
        error = SELECT_ERROR;
596
31.6k
        failf(data, "Proxy CONNECT aborted");
597
31.6k
      }
598
31.6k
      ts->keepon = KEEPON_DONE;
599
31.6k
      break;
600
31.7k
    }
601
602
3.31M
    if(ts->keepon == KEEPON_IGNORE) {
603
      /* This means we are currently ignoring a response-body */
604
149k
      if(ts->chunked_encoding) {
605
        /* chunked-encoded body, so we need to do the chunked dance
606
           properly to know when the end of the body is reached */
607
145k
        size_t consumed = 0;
608
609
        /* now parse the chunked piece of data so that we can
610
           properly tell when the stream ends */
611
145k
        result = Curl_httpchunk_read(data, &ts->ch, &byte, 1, &consumed);
612
145k
        if(result)
613
118
          return result;
614
145k
        if(Curl_httpchunk_is_done(data, &ts->ch)) {
615
          /* we are done reading chunks! */
616
8
          infof(data, "chunk reading DONE");
617
8
          ts->keepon = KEEPON_DONE;
618
8
        }
619
145k
      }
620
3.72k
      else if(ts->cl) {
621
        /* A Content-Length based body: count down the counter
622
           and make sure to break out of the loop when we are done! */
623
3.72k
        ts->cl--;
624
3.72k
        if(ts->cl <= 0) {
625
21
          ts->keepon = KEEPON_DONE;
626
21
          break;
627
21
        }
628
3.72k
      }
629
149k
      continue;
630
149k
    }
631
632
3.16M
    if(ts->maybe_folded) {
633
84.7k
      if(ISBLANK(byte)) {
634
13.0k
        Curl_http_to_fold(&ts->rcvbuf);
635
13.0k
        ts->leading_unfold = TRUE;
636
13.0k
      }
637
71.6k
      else {
638
71.6k
        result = single_header(cf, data, ts);
639
71.6k
        if(result)
640
973
          return result;
641
        /* now handle the new byte */
642
71.6k
      }
643
83.7k
      ts->maybe_folded = FALSE;
644
83.7k
    }
645
646
3.16M
    if(ts->leading_unfold) {
647
97.7k
      if(ISBLANK(byte))
648
        /* skip a bit brother */
649
84.8k
        continue;
650
      /* non-blank, insert a space then continue the unfolding */
651
12.9k
      if(curlx_dyn_addn(&ts->rcvbuf, " ", 1)) {
652
0
        failf(data, "CONNECT response too large");
653
0
        return CURLE_RECV_ERROR;
654
0
      }
655
12.9k
      ts->leading_unfold = FALSE;
656
12.9k
    }
657
3.08M
    if(curlx_dyn_addn(&ts->rcvbuf, &byte, 1)) {
658
29
      failf(data, "CONNECT response too large");
659
29
      return CURLE_RECV_ERROR;
660
29
    }
661
662
    /* if this is not the end of a header line then continue */
663
3.08M
    if(byte != 0x0a)
664
2.99M
      continue;
665
86.4k
    else {
666
86.4k
      const char *linep = curlx_dyn_ptr(&ts->rcvbuf);
667
86.4k
      size_t hlen = curlx_dyn_len(&ts->rcvbuf);
668
86.4k
      if(hlen && ISNEWLINE(linep[0])) {
669
        /* end of headers */
670
1.36k
        result = single_header(cf, data, ts);
671
1.36k
        if(result)
672
18
          return result;
673
1.36k
      }
674
85.0k
      else
675
85.0k
        ts->maybe_folded = TRUE;
676
86.4k
    }
677
678
86.4k
    if(result)
679
0
      return result;
680
86.4k
  } /* while there is buffer left and loop is requested */
681
682
32.6k
  if(error)
683
31.6k
    result = CURLE_RECV_ERROR;
684
32.6k
  *done = (ts->keepon == KEEPON_DONE);
685
32.6k
  if(!result && *done &&
686
986
     data->info.httpproxycode / 100 != 2 &&
687
775
     !(h1_proxy_is_udp(cf) && data->info.httpproxycode == 101)) {
688
    /* Deal with the possibly already received authenticate
689
       headers. 'newurl' is set to a new URL if we must loop. */
690
774
    result = Curl_http_auth_act(data);
691
774
  }
692
32.6k
  return result;
693
39.0k
}
694
695
static CURLcode H1_CONNECT(struct Curl_cfilter *cf,
696
                           struct Curl_easy *data,
697
                           struct h1_tunnel_state *ts)
698
39.1k
{
699
39.1k
  struct connectdata *conn = cf->conn;
700
39.1k
  CURLcode result;
701
39.1k
  bool done;
702
703
39.1k
  if(tunnel_is_established(ts))
704
0
    return CURLE_OK;
705
39.1k
  if(tunnel_is_failed(ts))
706
0
    return CURLE_RECV_ERROR; /* Need a cfilter close and new bootstrap */
707
708
39.1k
  do {
709
710
39.1k
    if(Curl_timeleft_ms(data) < 0) {
711
0
      failf(data, "Proxy CONNECT aborted due to timeout");
712
0
      result = CURLE_OPERATION_TIMEDOUT;
713
0
      goto out;
714
0
    }
715
716
39.1k
    switch(ts->tunnel_state) {
717
34.2k
    case H1_TUNNEL_INIT:
718
      /* Prepare the CONNECT request and make a first attempt to send. */
719
34.2k
      CURL_TRC_CF(data, cf, "CONNECT start");
720
34.2k
      result = start_CONNECT(cf, data, ts);
721
34.2k
      if(result)
722
108
        goto out;
723
34.1k
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_CONNECT, data);
724
34.1k
      FALLTHROUGH();
725
726
34.1k
    case H1_TUNNEL_CONNECT:
727
      /* see that the request is completely sent */
728
34.1k
      CURL_TRC_CF(data, cf, "CONNECT send");
729
34.1k
      result = send_CONNECT(cf, data, ts, &done);
730
34.1k
      if(result || !done)
731
4
        goto out;
732
34.1k
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_RECEIVE, data);
733
34.1k
      FALLTHROUGH();
734
735
39.0k
    case H1_TUNNEL_RECEIVE:
736
      /* read what is there */
737
39.0k
      CURL_TRC_CF(data, cf, "CONNECT receive");
738
39.0k
      result = recv_CONNECT_resp(cf, data, ts, &done);
739
39.0k
      if(result)
740
32.9k
        CURL_TRC_CF(data, cf, "error receiving CONNECT response: %d",
741
39.0k
                    (int)result);
742
39.0k
      if(!result)
743
6.09k
        result = Curl_pgrsUpdate(data);
744
      /* error or not complete yet. return for more multi-multi */
745
39.0k
      if(result || !done)
746
38.1k
        goto out;
747
      /* got it */
748
890
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_RESPONSE, data);
749
890
      FALLTHROUGH();
750
751
890
    case H1_TUNNEL_RESPONSE:
752
890
      CURL_TRC_CF(data, cf, "CONNECT response");
753
890
      if(data->req.newurl) {
754
        /* not the "final" response, we need to do a follow up request.
755
         * If the other side indicated a connection close, or if someone
756
         * else told us to close this connection, do so now.
757
         */
758
5
        Curl_req_soft_reset(&data->req, data);
759
5
        if(ts->close_connection || conn->bits.close) {
760
          /* Close this filter and the sub-chain, re-connect the
761
           * sub-chain and continue. Closing this filter will
762
           * reset our tunnel state. To avoid recursion, we return
763
           * and expect to be called again.
764
           */
765
0
          CURL_TRC_CF(data, cf, "CONNECT need to close+open");
766
0
          infof(data, "Connect me again please");
767
0
          return CURLE_AGAIN;
768
0
        }
769
5
        else {
770
          /* staying on this connection, reset state */
771
5
          h1_tunnel_go_state(cf, ts, H1_TUNNEL_INIT, data);
772
5
        }
773
5
      }
774
890
      break;
775
776
890
    default:
777
0
      break;
778
39.1k
    }
779
780
39.1k
  } while(data->req.newurl);
781
782
885
  DEBUGASSERT(ts->tunnel_state == H1_TUNNEL_RESPONSE);
783
885
  if(h1_proxy_is_udp(cf)) {
784
    /* RFC 9298: Accept 101 Upgrade for HTTP/1.1 and
785
     * 2xx responses for HTTP/2 and HTTP/3 proxies. */
786
77
    if(data->info.httpproxycode / 100 != 2 &&
787
75
       data->info.httpproxycode != 101) {
788
74
      curlx_safefree(data->req.newurl);
789
74
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
790
74
      failf(data, "CONNECT-UDP tunnel failed, response %d",
791
74
            data->req.httpcode);
792
74
      return CURLE_COULDNT_CONNECT;
793
74
    }
794
77
  }
795
808
  else {
796
808
    if(data->info.httpproxycode / 100 != 2) {
797
      /* a non-2xx response and we have no next URL to try. */
798
599
      curlx_safefree(data->req.newurl);
799
599
      h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
800
599
      failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode);
801
599
      return CURLE_COULDNT_CONNECT;
802
599
    }
803
808
  }
804
  /* 2xx response, SUCCESS! */
805
  /* 101 Switching Protocol for CONNECT-UDP */
806
212
  h1_tunnel_go_state(cf, ts, H1_TUNNEL_ESTABLISHED, data);
807
212
  if(h1_proxy_is_udp(cf))
808
3
    infof(data, "CONNECT-UDP tunnel established, response %d",
809
212
                                    data->info.httpproxycode);
810
209
  else
811
209
    infof(data, "CONNECT tunnel established, response %d",
812
212
                                    data->info.httpproxycode);
813
212
  result = CURLE_OK;
814
815
38.5k
out:
816
38.5k
  if(result)
817
33.0k
    h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
818
38.5k
  return result;
819
212
}
820
821
static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf,
822
                                    struct Curl_easy *data,
823
                                    bool *done)
824
39.1k
{
825
39.1k
  CURLcode result;
826
39.1k
  struct cf_h1_proxy_ctx *pctx = cf->ctx;
827
39.1k
  struct h1_tunnel_state *ts = pctx->ts;
828
829
39.1k
  if(cf->connected) {
830
0
    *done = TRUE;
831
0
    return CURLE_OK;
832
0
  }
833
834
39.1k
  CURL_TRC_CF(data, cf, "connect");
835
39.1k
  result = cf->next->cft->do_connect(cf->next, data, done);
836
39.1k
  if(result || !*done)
837
0
    return result;
838
839
39.1k
  *done = FALSE;
840
39.1k
  if(!ts) {
841
0
    result = tunnel_init(cf, data, &ts);
842
0
    if(result)
843
0
      return result;
844
0
    pctx->ts = ts;
845
0
  }
846
847
  /* We want "seamless" operations through HTTP proxy tunnel */
848
849
39.1k
  result = H1_CONNECT(cf, data, ts);
850
39.1k
  if(result)
851
33.7k
    goto out;
852
5.42k
  curlx_safefree(data->req.hd_proxy_auth);
853
854
39.1k
out:
855
39.1k
  *done = (result == CURLE_OK) && tunnel_is_established(pctx->ts);
856
39.1k
  if(*done) {
857
212
    cf->connected = TRUE;
858
    /* The real request will follow the CONNECT, reset request partially */
859
212
    Curl_req_soft_reset(&data->req, data);
860
212
    Curl_client_reset(data);
861
212
    Curl_pgrsReset(data);
862
212
    cf_tunnel_free(cf, data);
863
212
  }
864
39.1k
  return result;
865
5.42k
}
866
867
static CURLcode cf_h1_proxy_adjust_pollset(struct Curl_cfilter *cf,
868
                                           struct Curl_easy *data,
869
                                           struct easy_pollset *ps)
870
5.28k
{
871
5.28k
  struct cf_h1_proxy_ctx *pctx = cf->ctx;
872
5.28k
  struct h1_tunnel_state *ts = pctx->ts;
873
5.28k
  CURLcode result = CURLE_OK;
874
875
5.28k
  if(!cf->connected) {
876
    /* If we are not connected, but the filter "below" is
877
     * and not waiting on something, we are tunneling. */
878
5.21k
    curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
879
5.21k
    if(ts) {
880
      /* when we have sent a CONNECT to a proxy, we should rather either
881
         wait for the socket to become readable to be able to get the
882
         response headers or if we are still sending the request, wait
883
         for write. */
884
5.21k
      if(tunnel_want_send(ts))
885
4
        result = Curl_pollset_set_out_only(data, ps, sock);
886
5.20k
      else
887
5.20k
        result = Curl_pollset_set_in_only(data, ps, sock);
888
5.21k
    }
889
0
    else
890
0
      result = Curl_pollset_set_out_only(data, ps, sock);
891
5.21k
  }
892
71
  else {
893
71
    if(cf->next)
894
71
      result = cf->next->cft->adjust_pollset(cf->next, data, ps);
895
71
  }
896
5.28k
  return result;
897
5.28k
}
898
899
static bool cf_h1_proxy_data_pending(struct Curl_cfilter *cf,
900
                                     const struct Curl_easy *data)
901
444
{
902
444
  return cf->next ? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
903
444
}
904
905
static void cf_h1_proxy_destroy(struct Curl_cfilter *cf,
906
                                struct Curl_easy *data)
907
34.2k
{
908
34.2k
  CURL_TRC_CF(data, cf, "destroy");
909
34.2k
  cf_tunnel_free(cf, data);
910
34.2k
  curlx_safefree(cf->ctx);
911
34.2k
}
912
913
static CURLcode cf_h1_proxy_query(struct Curl_cfilter *cf,
914
                                  struct Curl_easy *data,
915
                                  int query, int *pres1, void *pres2)
916
17.2k
{
917
17.2k
  struct cf_h1_proxy_ctx *pctx = cf->ctx;
918
17.2k
  struct h1_tunnel_state *ts = pctx ? pctx->ts : NULL;
919
17.2k
  switch(query) {
920
0
  case CF_QUERY_HOST_PORT:
921
0
    if(!ts || !ts->dest)
922
0
      break;
923
0
    *pres1 = (int)ts->dest->port;
924
0
    *((const char **)pres2) = ts->dest->hostname;
925
0
    return CURLE_OK;
926
71
  case CF_QUERY_ALPN_NEGOTIATED: {
927
71
    const char **palpn = pres2;
928
71
    DEBUGASSERT(palpn);
929
71
    *palpn = NULL;
930
71
    return CURLE_OK;
931
71
  }
932
17.1k
  default:
933
17.1k
    break;
934
17.2k
  }
935
17.1k
  return cf->next ?
936
17.1k
    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
937
17.1k
    CURLE_UNKNOWN_OPTION;
938
17.2k
}
939
940
struct Curl_cftype Curl_cft_h1_proxy = {
941
  "H1-PROXY",
942
  CF_TYPE_IP_CONNECT | CF_TYPE_PROXY,
943
  0,
944
  cf_h1_proxy_destroy,
945
  cf_h1_proxy_connect,
946
  Curl_cf_def_shutdown,
947
  cf_h1_proxy_adjust_pollset,
948
  cf_h1_proxy_data_pending,
949
  Curl_cf_def_send,
950
  Curl_cf_def_recv,
951
  Curl_cf_def_cntrl,
952
  Curl_cf_def_conn_is_alive,
953
  Curl_cf_def_conn_keep_alive,
954
  cf_h1_proxy_query,
955
};
956
957
CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at,
958
                                       struct Curl_easy *data,
959
                                       struct Curl_peer *dest,
960
                                       int httpversion,
961
                                       bool udp_tunnel)
962
34.2k
{
963
34.2k
  struct Curl_cfilter *cf;
964
34.2k
  struct cf_h1_proxy_ctx *pctx;
965
34.2k
  struct h1_tunnel_state *ts;
966
34.2k
  CURLcode result;
967
968
34.2k
  (void)data;
969
34.2k
  if(!dest)
970
0
    return CURLE_FAILED_INIT;
971
34.2k
  if((httpversion < 10) || (httpversion >= 20))
972
0
    return CURLE_FAILED_INIT;
973
974
34.2k
  ts = curlx_calloc(1, sizeof(*ts));
975
34.2k
  if(!ts) {
976
0
    result = CURLE_OUT_OF_MEMORY;
977
0
    goto out;
978
0
  }
979
34.2k
  Curl_peer_link(&ts->dest, dest);
980
34.2k
  ts->httpversion = httpversion;
981
34.2k
  curlx_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS);
982
34.2k
  curlx_dyn_init(&ts->request_data, DYN_HTTP_REQUEST);
983
34.2k
  Curl_httpchunk_init(data, &ts->ch, TRUE, TRUE);
984
985
34.2k
  pctx = curlx_calloc(1, sizeof(*pctx));
986
34.2k
  if(!pctx) {
987
0
    result = CURLE_OUT_OF_MEMORY;
988
0
    goto out;
989
0
  }
990
34.2k
  pctx->udp_tunnel = udp_tunnel;
991
34.2k
  pctx->ts = ts;
992
34.2k
  result = Curl_cf_create(&cf, &Curl_cft_h1_proxy, pctx);
993
34.2k
  if(result) {
994
0
    curlx_free(pctx);
995
0
    goto out;
996
0
  }
997
34.2k
  ts = NULL;
998
34.2k
  Curl_conn_cf_insert_after(cf_at, cf);
999
1000
34.2k
out:
1001
34.2k
  tunnel_free(ts, data);
1002
34.2k
  return result;
1003
34.2k
}
1004
1005
#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */