Coverage Report

Created: 2026-08-31 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/http_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
#include "http_proxy.h"
27
28
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY)
29
30
#include "curl_trc.h"
31
#include "http.h"
32
#include "url.h"
33
#include "cfilters.h"
34
#include "cf-h1-proxy.h"
35
#include "cf-h2-proxy.h"
36
#include "connect.h"
37
#include "vauth/vauth.h"
38
#include "vquic/vquic.h"
39
#include "curlx/strparse.h"
40
41
static CURLcode dynhds_add_custom(struct Curl_easy *data,
42
                                  bool is_connect, int httpversion,
43
                                  bool is_udp, struct dynhds *hds)
44
34.1k
{
45
34.1k
  struct connectdata *conn = data->conn;
46
34.1k
  struct curl_slist *h[2];
47
34.1k
  struct curl_slist *headers;
48
34.1k
  int numlists = 1; /* by default */
49
34.1k
  int i;
50
51
34.1k
  enum Curl_proxy_use proxy;
52
53
34.1k
  if(is_connect && !is_udp)
54
29.5k
    proxy = HEADER_CONNECT;
55
4.62k
  else if(is_connect && is_udp)
56
4.62k
    proxy = HEADER_CONNECT_UDP;
57
0
  else
58
0
    proxy = conn->bits.origin_is_proxy ? HEADER_PROXY : HEADER_SERVER;
59
60
34.1k
  switch(proxy) {
61
0
  case HEADER_SERVER:
62
0
    h[0] = data->set.headers;
63
0
    break;
64
0
  case HEADER_PROXY:
65
0
    h[0] = data->set.headers;
66
0
    if(data->set.sep_headers) {
67
0
      h[1] = data->set.proxyheaders;
68
0
      numlists++;
69
0
    }
70
0
    break;
71
29.5k
  case HEADER_CONNECT:
72
29.5k
    if(data->set.sep_headers)
73
27.5k
      h[0] = data->set.proxyheaders;
74
1.98k
    else
75
1.98k
      h[0] = data->set.headers;
76
29.5k
    break;
77
4.62k
  case HEADER_CONNECT_UDP:
78
4.62k
    if(data->set.sep_headers)
79
4.42k
      h[0] = data->set.proxyheaders;
80
205
    else
81
205
      h[0] = data->set.headers;
82
4.62k
    break;
83
34.1k
  }
84
85
  /* loop through one or two lists */
86
68.3k
  for(i = 0; i < numlists; i++) {
87
210k
    for(headers = h[i]; headers; headers = headers->next) {
88
176k
      struct Curl_str name;
89
176k
      const char *value = NULL;
90
176k
      size_t valuelen = 0;
91
176k
      const char *ptr = headers->data;
92
93
      /* There are 2 quirks in place for custom headers:
94
       * 1. setting only 'name:' to suppress a header from being sent
95
       * 2. setting only 'name;' to send an empty (illegal) header
96
       */
97
176k
      if(!curlx_str_cspn(&ptr, &name, ";:")) {
98
159k
        if(!curlx_str_single(&ptr, ':')) {
99
69.1k
          curlx_str_passblanks(&ptr);
100
69.1k
          if(*ptr) {
101
66.8k
            value = ptr;
102
66.8k
            valuelen = strlen(value);
103
66.8k
          }
104
2.31k
          else {
105
            /* quirk #1, suppress this header */
106
2.31k
            continue;
107
2.31k
          }
108
69.1k
        }
109
89.8k
        else if(!curlx_str_single(&ptr, ';')) {
110
83.1k
          curlx_str_passblanks(&ptr);
111
83.1k
          if(!*ptr) {
112
            /* quirk #2, send an empty header */
113
79.6k
            value = "";
114
79.6k
            valuelen = 0;
115
79.6k
          }
116
3.49k
          else {
117
            /* this may be used for something else in the future,
118
             * ignore this for now */
119
3.49k
            continue;
120
3.49k
          }
121
83.1k
        }
122
6.71k
        else
123
          /* neither : nor ; in provided header value. We ignore this
124
           * silently */
125
6.71k
          continue;
126
159k
      }
127
17.7k
      else
128
        /* no name, move on */
129
17.7k
        continue;
130
131
146k
      DEBUGASSERT(curlx_strlen(&name) && value);
132
      /* trim surrounding whitespace so a padded field name (e.g.
133
         `Authorization :`) cannot slip past the Authorization/Cookie check */
134
146k
      curlx_str_trimblanks(&name);
135
146k
      if(data->state.http_host &&
136
         /* a Host: header was sent already, do not pass on any custom Host:
137
            header as that will produce *two* in the same request! */
138
154
         curlx_str_casecompare(&name, "Host"))
139
3
        ;
140
146k
      else if(data->state.httpreq == HTTPREQ_POST_FORM &&
141
              /* this header (extended by formdata.c) is sent later */
142
10.7k
              curlx_str_casecompare(&name, "Content-Type"))
143
55
        ;
144
146k
      else if(data->state.httpreq == HTTPREQ_POST_MIME &&
145
              /* this header is sent later */
146
62.8k
              curlx_str_casecompare(&name, "Content-Type"))
147
62
        ;
148
146k
      else if(data->req.authneg &&
149
              /* while doing auth neg, do not allow the custom length since
150
                 we will force length zero then */
151
0
              curlx_str_casecompare(&name, "Content-Length"))
152
0
        ;
153
146k
      else if((httpversion >= 20) &&
154
0
              curlx_str_casecompare(&name, "Transfer-Encoding"))
155
0
        ;
156
      /* HTTP/2 and HTTP/3 do not support chunked requests */
157
146k
      else if((curlx_str_casecompare(&name, "Authorization") ||
158
146k
               curlx_str_casecompare(&name, "Cookie")) &&
159
              /* be careful of sending this potentially sensitive header to
160
                 other hosts */
161
4.38k
              !Curl_auth_allowed_to_host(data))
162
29
        ;
163
146k
      else {
164
146k
        CURLcode result =
165
146k
          Curl_dynhds_add(hds, curlx_str(&name), curlx_strlen(&name),
166
146k
                          value, valuelen);
167
146k
        if(result)
168
0
          return result;
169
146k
      }
170
146k
    }
171
34.1k
  }
172
173
34.1k
  return CURLE_OK;
174
34.1k
}
175
176
struct cf_proxy_ctx {
177
  struct Curl_peer *peer; /* proxy */
178
  struct Curl_peer *tunnel_peer; /* tunnel destination */
179
  uint8_t proxytype;
180
  uint8_t tunnel_transport;
181
  BIT(sub_filter_installed);
182
};
183
184
static int proxy_http_ver_major(proxy_http_ver ver)
185
34.2k
{
186
34.2k
  switch(ver) {
187
34.2k
  case PROXY_HTTP_V1:
188
34.2k
    return 11;
189
0
  case PROXY_HTTP_V2:
190
0
    return 20;
191
0
  case PROXY_HTTP_V3:
192
0
    return 30;
193
34.2k
  }
194
0
  return 0;
195
34.2k
}
196
197
static CURLcode http_proxy_create_CONNECT(struct httpreq **preq,
198
                                          struct Curl_cfilter *cf,
199
                                          struct Curl_easy *data,
200
                                          struct Curl_peer *dest,
201
                                          proxy_http_ver ver)
202
29.6k
{
203
29.6k
  char *authority = NULL;
204
29.6k
  const char *ua;
205
29.6k
  int httpversion = proxy_http_ver_major(ver);
206
29.6k
  CURLcode result;
207
29.6k
  struct httpreq *req = NULL;
208
209
29.6k
  authority = curl_maprintf("%s%s%s:%u",
210
29.6k
                            dest->ipv6 ? "[" : "",
211
29.6k
                            dest->hostname,
212
29.6k
                            dest->ipv6 ? "]" : "",
213
29.6k
                            dest->port);
214
29.6k
  if(!authority) {
215
0
    result = CURLE_OUT_OF_MEMORY;
216
0
    goto out;
217
0
  }
218
219
29.6k
  result = Curl_http_req_make(&req, "CONNECT", CURL_CSTRLEN("CONNECT"),
220
29.6k
                              NULL, 0, authority, strlen(authority),
221
29.6k
                              NULL, 0);
222
29.6k
  if(result)
223
0
    goto out;
224
225
  /* Setup the proxy-authorization header, if any */
226
29.6k
  result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET,
227
29.6k
                                 req->authority, NULL, TRUE);
228
29.6k
  if(result)
229
105
    goto out;
230
231
  /* If user is not overriding Host: header, we add for HTTP/1.x */
232
29.5k
  if(ver == PROXY_HTTP_V1 &&
233
29.5k
     !Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) {
234
29.4k
    result = Curl_dynhds_cadd(&req->headers, "Host", authority);
235
29.4k
    if(result)
236
0
      goto out;
237
29.4k
  }
238
239
29.5k
  if(data->req.hd_proxy_auth) {
240
667
    result = Curl_dynhds_h1_cadd_line(&req->headers,
241
667
                                      data->req.hd_proxy_auth);
242
667
    if(result)
243
0
      goto out;
244
667
  }
245
246
29.5k
  ua = CURL_EASY_STR(data, STRING_USERAGENT);
247
29.5k
  if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) &&
248
29.5k
     ua && *ua) {
249
78
    result = Curl_dynhds_cadd(&req->headers, "User-Agent", ua);
250
78
    if(result)
251
0
      goto out;
252
78
  }
253
254
29.5k
  if(ver == PROXY_HTTP_V1 &&
255
29.5k
     !Curl_checkProxyheaders(data, cf->conn, STRCONST("Proxy-Connection"))) {
256
29.5k
    result = Curl_dynhds_cadd(&req->headers, "Proxy-Connection", "Keep-Alive");
257
29.5k
    if(result)
258
0
      goto out;
259
29.5k
  }
260
261
29.5k
  result = dynhds_add_custom(data, TRUE, httpversion,
262
29.5k
                             FALSE, &req->headers);
263
264
29.6k
out:
265
29.6k
  if(result && req) {
266
105
    Curl_http_req_free(req);
267
105
    req = NULL;
268
105
  }
269
29.6k
  curlx_free(authority);
270
29.6k
  *preq = req;
271
29.6k
  return result;
272
29.5k
}
273
274
static CURLcode http_proxy_create_CONNECTUDP(struct httpreq **preq,
275
                                             struct Curl_cfilter *cf,
276
                                             struct Curl_easy *data,
277
                                             struct Curl_peer *dest,
278
                                             proxy_http_ver ver)
279
4.62k
{
280
4.62k
  const char *proxy_scheme = "http", *ua;
281
4.62k
  const char *proxy_host = cf->conn->http_proxy.peer->hostname;
282
4.62k
  int httpversion = proxy_http_ver_major(ver);
283
4.62k
  char *authority = NULL;
284
4.62k
  char *path = NULL;
285
4.62k
  char *encoded_host = NULL;
286
4.62k
  struct httpreq *req = NULL;
287
4.62k
  bool proxy_ipv6_ip;
288
4.62k
  CURLcode result;
289
290
4.62k
  if(cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS ||
291
4.62k
     cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS2 ||
292
4.62k
     cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS3)
293
0
    proxy_scheme = "https";
294
295
4.62k
  proxy_ipv6_ip = cf->conn->http_proxy.peer->ipv6 != 0;
296
297
4.62k
  authority = curl_maprintf("%s%s%s:%d",
298
4.62k
                            proxy_ipv6_ip ? "[" : "",
299
4.62k
                            proxy_host,
300
4.62k
                            proxy_ipv6_ip ? "]" : "",
301
4.62k
                            cf->conn->http_proxy.peer->port);
302
4.62k
  if(!authority) {
303
0
    result = CURLE_OUT_OF_MEMORY;
304
0
    goto out;
305
0
  }
306
307
4.62k
  if(dest->ipv6) {
308
    /* RFC 9298: colons in IPv6 addresses MUST be percent-encoded
309
     * in the URI template (e.g. "2001:db8::1" -> "2001%3Adb8%3A%3A1") */
310
0
    const char *s = dest->hostname;
311
0
    char *d;
312
0
    size_t hlen = strlen(s);
313
0
    encoded_host = curlx_malloc(hlen * 3 + 1);
314
0
    if(!encoded_host) {
315
0
      result = CURLE_OUT_OF_MEMORY;
316
0
      goto out;
317
0
    }
318
0
    d = encoded_host;
319
0
    while(*s) {
320
0
      if(*s == ':') {
321
0
        *d++ = '%';
322
0
        *d++ = '3';
323
0
        *d++ = 'A';
324
0
      }
325
0
      else
326
0
        *d++ = *s;
327
0
      s++;
328
0
    }
329
0
    *d = '\0';
330
0
    path = curl_maprintf("/.well-known/masque/udp/%s/%u/",
331
0
                         encoded_host, (unsigned int)dest->port);
332
0
  }
333
4.62k
  else {
334
4.62k
    path = curl_maprintf("/.well-known/masque/udp/%s/%u/",
335
4.62k
                         dest->hostname, (unsigned int)dest->port);
336
4.62k
  }
337
338
4.62k
  if(!path) {
339
0
    result = CURLE_OUT_OF_MEMORY;
340
0
    goto out;
341
0
  }
342
343
4.62k
  if(ver == PROXY_HTTP_V1) {
344
4.62k
    result = Curl_http_req_make(&req, "GET", CURL_CSTRLEN("GET"),
345
4.62k
                                proxy_scheme, strlen(proxy_scheme),
346
4.62k
                                authority, strlen(authority),
347
4.62k
                                path, strlen(path));
348
4.62k
    if(result)
349
0
      goto out;
350
4.62k
  }
351
0
  else if(ver == PROXY_HTTP_V2 || ver == PROXY_HTTP_V3) {
352
0
    result = Curl_http_req_make(&req, "CONNECT", CURL_CSTRLEN("CONNECT"),
353
0
                                proxy_scheme, strlen(proxy_scheme),
354
0
                                authority, strlen(authority),
355
0
                                path, strlen(path));
356
0
    if(result)
357
0
      goto out;
358
0
  }
359
0
  else {
360
0
    result = CURLE_FAILED_INIT;
361
0
    goto out;
362
0
  }
363
364
  /* Setup the proxy-authorization header, if any */
365
4.62k
  result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET,
366
4.62k
                                 req->authority, NULL, TRUE);
367
4.62k
  if(result)
368
3
    goto out;
369
370
  /* If user is not overriding Host: header, we add for HTTP/1.x */
371
4.62k
  if(ver == PROXY_HTTP_V1 &&
372
4.62k
     !Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) {
373
4.62k
    result = Curl_dynhds_cadd(&req->headers, "Host", authority);
374
4.62k
    if(result)
375
0
      goto out;
376
4.62k
  }
377
378
4.62k
  if(data->req.hd_proxy_auth) {
379
48
    result = Curl_dynhds_h1_cadd_line(&req->headers,
380
48
                                      data->req.hd_proxy_auth);
381
48
    if(result)
382
0
      goto out;
383
48
  }
384
385
4.62k
  ua = CURL_EASY_STR(data, STRING_USERAGENT);
386
4.62k
  if(ver == PROXY_HTTP_V1 &&
387
4.62k
     !Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) &&
388
4.62k
     ua && *ua) {
389
5
    result = Curl_dynhds_cadd(&req->headers, "User-Agent", ua);
390
5
    if(result)
391
0
      goto out;
392
5
  }
393
394
4.62k
  if(ver == PROXY_HTTP_V1 &&
395
4.62k
     !Curl_checkProxyheaders(data, cf->conn, STRCONST("Proxy-Connection"))) {
396
4.62k
    result = Curl_dynhds_cadd(&req->headers, "Proxy-Connection", "Keep-Alive");
397
4.62k
    if(result)
398
0
      goto out;
399
4.62k
  }
400
401
4.62k
  if(ver == PROXY_HTTP_V1) {
402
4.62k
    result = Curl_dynhds_cadd(&req->headers, "Connection", "Upgrade");
403
4.62k
    if(result)
404
0
      goto out;
405
406
4.62k
    result = Curl_dynhds_cadd(&req->headers, "Upgrade", "connect-udp");
407
4.62k
    if(result)
408
0
      goto out;
409
410
4.62k
    result = Curl_dynhds_cadd(&req->headers, "Capsule-Protocol", "?1");
411
4.62k
    if(result)
412
0
      goto out;
413
4.62k
  }
414
0
  else {
415
0
    result = Curl_dynhds_cadd(&req->headers, ":Protocol", "connect-udp");
416
0
    if(result)
417
0
      goto out;
418
419
0
    if(ver >= PROXY_HTTP_V2) {
420
0
      result = Curl_dynhds_cadd(&req->headers, "Capsule-Protocol", "?1");
421
0
      if(result)
422
0
        goto out;
423
0
    }
424
0
  }
425
426
4.62k
  result = dynhds_add_custom(data, TRUE, httpversion,
427
4.62k
                             TRUE, &req->headers);
428
429
4.62k
out:
430
4.62k
  if(result && req) {
431
3
    Curl_http_req_free(req);
432
3
    req = NULL;
433
3
  }
434
4.62k
  curlx_free(authority);
435
4.62k
  curlx_free(path);
436
4.62k
  curlx_free(encoded_host);
437
4.62k
  *preq = req;
438
4.62k
  return result;
439
4.62k
}
440
441
CURLcode Curl_http_proxy_create_tunnel_request(
442
    struct httpreq **preq, struct Curl_cfilter *cf,
443
    struct Curl_easy *data, struct Curl_peer *dest,
444
    proxy_http_ver ver, bool udp_tunnel)
445
34.2k
{
446
34.2k
  CURLcode result;
447
448
34.2k
  if(udp_tunnel)
449
4.62k
    result = http_proxy_create_CONNECTUDP(preq, cf, data, dest, ver);
450
29.6k
  else
451
29.6k
    result = http_proxy_create_CONNECT(preq, cf, data, dest, ver);
452
34.2k
  if(result)
453
108
    return result;
454
455
34.1k
  if(udp_tunnel)
456
4.62k
    infof(data, "Establishing %s proxy UDP tunnel to %s:%u",
457
34.1k
          (ver == PROXY_HTTP_V2) ? "HTTP/2" :
458
34.1k
          (ver == PROXY_HTTP_V3) ? "HTTP/3" : "HTTP",
459
34.1k
          dest->user_hostname, dest->port);
460
29.5k
  else
461
29.5k
    infof(data, "Establishing %s proxy tunnel to %s",
462
34.1k
          (ver == PROXY_HTTP_V2) ? "HTTP/2" :
463
34.1k
          (ver == PROXY_HTTP_V3) ? "HTTP/3" : "HTTP",
464
34.1k
          (*preq)->authority);
465
34.1k
  return CURLE_OK;
466
34.2k
}
467
468
CURLcode Curl_http_proxy_inspect_tunnel_response(
469
    struct Curl_cfilter *cf, struct Curl_easy *data,
470
    struct http_resp *resp, bool udp_tunnel,
471
    proxy_inspect_result *presult)
472
0
{
473
0
  struct dynhds_entry *capsule_protocol = NULL;
474
0
  struct dynhds_entry *auth_reply = NULL;
475
0
  size_t i, header_count;
476
0
  CURLcode result = CURLE_OK;
477
478
0
  DEBUGASSERT(resp);
479
480
0
  header_count = Curl_dynhds_count(&resp->headers);
481
0
  if(udp_tunnel)
482
0
    infof(data, "CONNECT-UDP Response Status %d", resp->status);
483
0
  else
484
0
    infof(data, "CONNECT Response Status %d", resp->status);
485
0
  infof(data, "Response Headers (%zu total):", header_count);
486
0
  for(i = 0; i < header_count; i++) {
487
0
    struct dynhds_entry *entry = Curl_dynhds_getn(&resp->headers, i);
488
0
    if(entry)
489
0
      infof(data, "  %s: %s", entry->name, entry->value);
490
0
  }
491
492
0
  if(resp->status == 401) {
493
0
    auth_reply = Curl_dynhds_cget(&resp->headers, "WWW-Authenticate");
494
0
  }
495
0
  else if(resp->status == 407) {
496
0
    auth_reply = Curl_dynhds_cget(&resp->headers, "Proxy-Authenticate");
497
0
  }
498
499
0
  if(auth_reply) {
500
0
    CURL_TRC_CF(data, cf, "[0] CONNECT%s: fwd auth header '%s'",
501
0
                udp_tunnel ? "-UDP" : "", auth_reply->value);
502
0
    result = Curl_http_input_auth(data, resp->status == 407,
503
0
                                  auth_reply->value);
504
0
    if(result)
505
0
      return result;
506
0
    if(data->req.newurl) {
507
0
      curlx_safefree(data->req.newurl);
508
0
      *presult = PROXY_INSPECT_AUTH_RETRY;
509
0
      return CURLE_OK;
510
0
    }
511
0
  }
512
513
0
  if(udp_tunnel) {
514
0
    if(resp->status / 100 == 2) {
515
0
      capsule_protocol = Curl_dynhds_cget(&resp->headers,
516
0
                                           "capsule-protocol");
517
0
      if(capsule_protocol) {
518
0
        if(!strncmp(capsule_protocol->value, "?1", 2) &&
519
0
           !capsule_protocol->value[2]) {
520
0
          infof(data, "CONNECT-UDP tunnel established, response %d",
521
0
                resp->status);
522
0
          *presult = PROXY_INSPECT_OK;
523
0
          return CURLE_OK;
524
0
        }
525
0
        failf(data, "Failed to establish CONNECT-UDP tunnel, response %d, "
526
0
              "unsupported capsule-protocol value '%s'",
527
0
              resp->status, capsule_protocol->value);
528
0
        *presult = PROXY_INSPECT_FAILED;
529
0
        return CURLE_COULDNT_CONNECT;
530
0
      }
531
0
      else {
532
        /* NOTE proxies may not set capsule protocol in the headers */
533
0
        infof(data, "CONNECT-UDP tunnel established, response %d "
534
0
                    "but no capsule-protocol header found", resp->status);
535
0
        *presult = PROXY_INSPECT_OK;
536
0
        return CURLE_OK;
537
0
      }
538
0
    }
539
0
    else {
540
0
      failf(data, "Failed to establish CONNECT-UDP tunnel, "
541
0
                  "response %d", resp->status);
542
0
      *presult = PROXY_INSPECT_FAILED;
543
0
      return CURLE_COULDNT_CONNECT;
544
0
    }
545
0
  }
546
547
0
  if(resp->status / 100 == 2) {
548
0
    infof(data, "CONNECT tunnel established, response %d", resp->status);
549
0
    *presult = PROXY_INSPECT_OK;
550
0
    return CURLE_OK;
551
0
  }
552
553
0
  *presult = PROXY_INSPECT_FAILED;
554
0
  return CURLE_COULDNT_CONNECT;
555
0
}
556
557
static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf,
558
                                      struct Curl_easy *data,
559
                                      bool *done)
560
39.2k
{
561
39.2k
  struct cf_proxy_ctx *ctx = cf->ctx;
562
39.2k
  CURLcode result;
563
39.2k
  bool udp_tunnel = TRNSPRT_IS_DGRAM(ctx->tunnel_transport);
564
39.2k
  const char *tunnel_type = udp_tunnel ? "CONNECT-UDP" : "CONNECT";
565
566
39.2k
  if(cf->connected) {
567
23
    *done = TRUE;
568
23
    return CURLE_OK;
569
23
  }
570
571
39.1k
  CURL_TRC_CF(data, cf, "%s", tunnel_type);
572
73.4k
connect_sub:
573
  /* in case of h3_proxy, cf->next will be NULL initially */
574
73.4k
  if(cf->next) {
575
73.4k
    result = cf->next->cft->do_connect(cf->next, data, done);
576
73.4k
    if(result || !*done)
577
38.9k
      return result;
578
73.4k
  }
579
580
34.4k
  *done = FALSE;
581
34.4k
  if(!ctx->sub_filter_installed) {
582
34.2k
    const char *alpn = NULL;
583
584
    /* in case of h3_proxy, cf->next will be NULL initially */
585
34.2k
    if(cf->next) {
586
34.2k
      alpn = Curl_conn_cf_get_alpn_negotiated(cf->next, data);
587
34.2k
    }
588
589
34.2k
    if(alpn)
590
0
      infof(data, "%s: '%s' negotiated", tunnel_type, alpn);
591
34.2k
    else if(!alpn) {
592
      /* No ALPN, proxytype rules. Fake ALPN */
593
34.2k
      infof(data, "%s: no ALPN negotiated", tunnel_type);
594
34.2k
      switch(ctx->proxytype) {
595
14
      case CURLPROXY_HTTP_1_0:
596
14
        alpn = "http/1.0";
597
14
        break;
598
0
      case CURLPROXY_HTTPS2:
599
0
        alpn = "h2";
600
0
        break;
601
0
      case CURLPROXY_HTTPS3:
602
0
        alpn = "h3";
603
0
        break;
604
34.2k
      default:
605
34.2k
        alpn = "http/1.1";
606
34.2k
        break;
607
34.2k
      }
608
34.2k
    }
609
610
34.2k
    if(!strcmp(alpn, "http/1.0")) {
611
14
      CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.0");
612
14
      result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->tunnel_peer, 10,
613
14
                                             udp_tunnel);
614
14
      if(result)
615
0
        goto out;
616
14
    }
617
34.2k
    else if(!strcmp(alpn, "http/1.1")) {
618
34.2k
      int httpversion = (ctx->proxytype == CURLPROXY_HTTP_1_0) ? 10 : 11;
619
34.2k
      CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.%d",
620
34.2k
                  httpversion % 10);
621
34.2k
      result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->tunnel_peer,
622
34.2k
                                             httpversion, udp_tunnel);
623
34.2k
      if(result)
624
0
        goto out;
625
34.2k
    }
626
0
#ifdef USE_NGHTTP2
627
0
    else if(!strcmp(alpn, "h2")) {
628
0
      CURL_TRC_CF(data, cf, "installing subfilter for HTTP/2");
629
0
      result = Curl_cf_h2_proxy_insert_after(cf, data, ctx->tunnel_peer,
630
0
                                             udp_tunnel);
631
0
      if(result)
632
0
        goto out;
633
0
    }
634
0
#endif /* USE_NGHTTP2 */
635
#if defined(USE_PROXY_HTTP3) && defined(USE_NGHTTP3) && \
636
  defined(USE_NGTCP2) && defined(USE_OPENSSL)
637
    else if(!strcmp(alpn, "h3")) {
638
      CURL_TRC_CF(data, cf, "installing subfilter for HTTP/3");
639
      result = Curl_cf_h3_proxy_insert_after(cf, data, ctx->peer, ctx->peer,
640
                                             ctx->tunnel_peer,
641
                                             ctx->tunnel_transport);
642
      if(result)
643
        goto out;
644
    }
645
#endif /* USE_PROXY_HTTP3 && USE_NGHTTP3 && USE_NGTCP2 && USE_OPENSSL */
646
0
    else {
647
0
      failf(data, "%s: negotiated ALPN '%s' not supported", tunnel_type, alpn);
648
0
      result = CURLE_COULDNT_CONNECT;
649
0
      goto out;
650
0
    }
651
652
34.2k
    ctx->sub_filter_installed = TRUE;
653
    /* after we installed the filter "below" us, we call connect
654
     * on out sub-chain again.
655
     */
656
34.2k
    goto connect_sub;
657
34.2k
  }
658
212
  else {
659
    /* subchain connected and we had already installed the protocol filter.
660
     * This means the protocol tunnel is established, we are done. */
661
212
    DEBUGASSERT(ctx->sub_filter_installed);
662
212
    result = CURLE_OK;
663
212
  }
664
665
212
out:
666
212
  if(!result) {
667
212
    cf->connected = TRUE;
668
212
    *done = TRUE;
669
212
  }
670
212
  return result;
671
34.4k
}
672
673
static CURLcode cf_http_proxy_query(struct Curl_cfilter *cf,
674
                                    struct Curl_easy *data,
675
                                    int query, int *pres1, void *pres2)
676
11.8k
{
677
11.8k
  struct cf_proxy_ctx *ctx = cf->ctx;
678
11.8k
  switch(query) {
679
0
  case CF_QUERY_HOST_PORT:
680
0
    *pres1 = (int)ctx->tunnel_peer->port;
681
0
    *((const char **)pres2) = ctx->tunnel_peer->hostname;
682
0
    return CURLE_OK;
683
0
  case CF_QUERY_ALPN_NEGOTIATED: {
684
0
    const char **palpn = pres2;
685
0
    DEBUGASSERT(palpn);
686
0
    *palpn = NULL;
687
0
    return CURLE_OK;
688
0
  }
689
11.8k
  default:
690
11.8k
    break;
691
11.8k
  }
692
11.8k
  return cf->next ?
693
11.8k
    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
694
11.8k
    CURLE_UNKNOWN_OPTION;
695
11.8k
}
696
697
static void cf_https_proxy_ctx_free(struct cf_proxy_ctx *ctx)
698
68.5k
{
699
68.5k
  if(ctx) {
700
34.2k
    Curl_peer_unlink(&ctx->peer);
701
34.2k
    Curl_peer_unlink(&ctx->tunnel_peer);
702
34.2k
    curlx_free(ctx);
703
34.2k
  }
704
68.5k
}
705
706
static void http_proxy_cf_destroy(struct Curl_cfilter *cf,
707
                                  struct Curl_easy *data)
708
34.2k
{
709
34.2k
  struct cf_proxy_ctx *ctx = cf->ctx;
710
34.2k
  if(ctx) {
711
34.2k
    CURL_TRC_CF(data, cf, "destroy");
712
34.2k
    cf_https_proxy_ctx_free(ctx);
713
34.2k
  }
714
34.2k
}
715
716
struct Curl_cftype Curl_cft_http_proxy = {
717
  "HTTP-PROXY",
718
  CF_TYPE_IP_CONNECT | CF_TYPE_PROXY | CF_TYPE_SETUP,
719
  0,
720
  http_proxy_cf_destroy,
721
  http_proxy_cf_connect,
722
  Curl_cf_def_shutdown,
723
  Curl_cf_def_adjust_pollset,
724
  Curl_cf_def_data_pending,
725
  Curl_cf_def_send,
726
  Curl_cf_def_recv,
727
  Curl_cf_def_cntrl,
728
  Curl_cf_def_conn_is_alive,
729
  Curl_cf_def_conn_keep_alive,
730
  cf_http_proxy_query,
731
};
732
733
CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at,
734
                                         struct Curl_easy *data,
735
                                         struct Curl_peer *peer,
736
                                         struct Curl_peer *tunnel_peer,
737
                                         uint8_t tunnel_transport,
738
                                         uint8_t proxytype)
739
34.2k
{
740
34.2k
  struct Curl_cfilter *cf;
741
34.2k
  struct cf_proxy_ctx *ctx = NULL;
742
34.2k
  CURLcode result;
743
744
34.2k
  (void)data;
745
34.2k
  if(!peer || !tunnel_peer)
746
0
    return CURLE_FAILED_INIT;
747
748
34.2k
  ctx = curlx_calloc(1, sizeof(*ctx));
749
34.2k
  if(!ctx) {
750
0
    result = CURLE_OUT_OF_MEMORY;
751
0
    goto out;
752
0
  }
753
34.2k
  Curl_peer_link(&ctx->peer, peer);
754
34.2k
  Curl_peer_link(&ctx->tunnel_peer, tunnel_peer);
755
34.2k
  ctx->proxytype = proxytype;
756
34.2k
  ctx->tunnel_transport = tunnel_transport;
757
758
34.2k
  result = Curl_cf_create(&cf, &Curl_cft_http_proxy, ctx);
759
34.2k
  if(result)
760
0
    goto out;
761
34.2k
  ctx = NULL;
762
34.2k
  Curl_conn_cf_insert_after(cf_at, cf);
763
764
34.2k
out:
765
34.2k
  cf_https_proxy_ctx_free(ctx);
766
34.2k
  return result;
767
34.2k
}
768
769
uint8_t Curl_http_proxy_transport(uint8_t proxytype)
770
35.1k
{
771
35.1k
  switch(proxytype) {
772
0
  case CURLPROXY_HTTPS3:
773
0
    return TRNSPRT_QUIC;
774
35.1k
  default:
775
35.1k
    return TRNSPRT_TCP;
776
35.1k
  }
777
35.1k
}
778
779
#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */